From 6465307d0ad616ee58090d52a1e3e86065e2c90a Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 15 Oct 2024 12:07:48 +0300 Subject: [PATCH 001/106] Updated on 2026-08-14 --- build.gradle.kts | 12 + core/ui/build.gradle.kts | 5 + .../AmountVisualTransformation.kt | 33 +- .../bigdecimal/BigDecimalCompactFormat.kt | 159 ++++++++ .../bigdecimal/BigDecimalCryptoFormat.kt | 218 ++++++++++ .../format/bigdecimal/BigDecimalFiatFormat.kt | 144 +++++++ .../ui/format/bigdecimal/BigDecimalFormat.kt | 29 ++ .../bigdecimal/BigDecimalFormatConstants.kt | 20 + .../bigdecimal/BigDecimalPercentFormat.kt | 39 ++ .../tangem/core/ui/format/bigdecimal/Utils.kt | 15 + .../core/ui/utils/BigDecimalFormatter.kt | 287 +------------- .../bigdecimal/BigDecimalCryptoFormatTest.kt | 373 ++++++++++++++++++ .../bigdecimal/BigDecimalFiatFormatTest.kt | 297 ++++++++++++++ .../format/bigdecimal/BigDecimalFormatTest.kt | 29 ++ .../bigdecimal/BigDecimalPercentFormatTest.kt | 70 ++++ .../core/ui/format/bigdecimal/TestUtils.kt | 7 + .../model/converters/InsightsConverter.kt | 22 +- .../impl/model/converters/MetricsConverter.kt | 26 +- .../converters/MarketsTokenItemConverter.kt | 17 +- 19 files changed, 1499 insertions(+), 303 deletions(-) create mode 100644 core/ui/src/main/java/com/tangem/core/ui/format/bigdecimal/BigDecimalCompactFormat.kt create mode 100644 core/ui/src/main/java/com/tangem/core/ui/format/bigdecimal/BigDecimalCryptoFormat.kt create mode 100644 core/ui/src/main/java/com/tangem/core/ui/format/bigdecimal/BigDecimalFiatFormat.kt create mode 100644 core/ui/src/main/java/com/tangem/core/ui/format/bigdecimal/BigDecimalFormat.kt create mode 100644 core/ui/src/main/java/com/tangem/core/ui/format/bigdecimal/BigDecimalFormatConstants.kt create mode 100644 core/ui/src/main/java/com/tangem/core/ui/format/bigdecimal/BigDecimalPercentFormat.kt create mode 100644 core/ui/src/main/java/com/tangem/core/ui/format/bigdecimal/Utils.kt create mode 100644 core/ui/src/test/kotlin/com/tangem/core/ui/format/bigdecimal/BigDecimalCryptoFormatTest.kt create mode 100644 core/ui/src/test/kotlin/com/tangem/core/ui/format/bigdecimal/BigDecimalFiatFormatTest.kt create mode 100644 core/ui/src/test/kotlin/com/tangem/core/ui/format/bigdecimal/BigDecimalFormatTest.kt create mode 100644 core/ui/src/test/kotlin/com/tangem/core/ui/format/bigdecimal/BigDecimalPercentFormatTest.kt create mode 100644 core/ui/src/test/kotlin/com/tangem/core/ui/format/bigdecimal/TestUtils.kt diff --git a/build.gradle.kts b/build.gradle.kts index e71d217293..188c0a6ef6 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,3 +1,5 @@ +import org.gradle.api.tasks.testing.logging.TestExceptionFormat + plugins { alias(deps.plugins.kotlin.android) apply false alias(deps.plugins.kotlin.jvm) apply false @@ -20,6 +22,16 @@ interface Injected { val fs: FileSystemOperations } +// Test Logging +subprojects { + tasks.withType { + testLogging { + exceptionFormat = TestExceptionFormat.FULL + showStandardStreams = true + } + } +} + val assembleInternalQA by tasks.registering { group = "build" description = "Builds internal APK to 'build/outputs' directory" diff --git a/core/ui/build.gradle.kts b/core/ui/build.gradle.kts index 3781bde624..c7fb1b5984 100644 --- a/core/ui/build.gradle.kts +++ b/core/ui/build.gradle.kts @@ -52,4 +52,9 @@ dependencies { api(deps.jodatime) implementation(deps.timber) implementation(deps.markdown) + + /** Tests */ + testImplementation(deps.test.junit) + testImplementation(deps.test.mockk) + testImplementation(deps.test.truth) } \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/fields/visualtransformations/AmountVisualTransformation.kt b/core/ui/src/main/java/com/tangem/core/ui/components/fields/visualtransformations/AmountVisualTransformation.kt index 8f10352030..17521b19b9 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/fields/visualtransformations/AmountVisualTransformation.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/fields/visualtransformations/AmountVisualTransformation.kt @@ -4,10 +4,15 @@ import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.input.OffsetMapping import androidx.compose.ui.text.input.TransformedText import androidx.compose.ui.text.input.VisualTransformation -import com.tangem.core.ui.utils.BigDecimalFormatter +import com.tangem.core.ui.format.bigdecimal.BigDecimalFormatConstants +import com.tangem.core.ui.format.bigdecimal.BigDecimalFormatConstants.CURRENCY_SPACE +import com.tangem.core.ui.format.bigdecimal.getJavaCurrencyByCode import com.tangem.core.ui.utils.defaultFormat import com.tangem.core.ui.utils.formatWithThousands +import timber.log.Timber import java.text.DecimalFormat +import java.text.NumberFormat +import java.util.Locale class AmountVisualTransformation( private val decimals: Int, @@ -25,13 +30,13 @@ class AmountVisualTransformation( val formattedText = if (formattedAmount.isNotEmpty() && symbol != null) { AnnotatedString( if (currencyCode != null) { - BigDecimalFormatter.formatFiatEditableAmount( + formatFiatEditableAmount( fiatAmount = formattedAmount, fiatCurrencyCode = currencyCode, fiatCurrencySymbol = symbol, ) } else { - BigDecimalFormatter.formatWithSymbol(formattedAmount, symbol) + formatWithSymbol(formattedAmount, symbol) }, ) } else { @@ -45,6 +50,28 @@ class AmountVisualTransformation( ) } + private fun formatFiatEditableAmount( + fiatAmount: String?, + fiatCurrencyCode: String, + fiatCurrencySymbol: String, + locale: Locale = Locale.getDefault(), + ): String { + if (fiatAmount == null) return BigDecimalFormatConstants.EMPTY_BALANCE_SIGN + + val formatterCurrency = getJavaCurrencyByCode(fiatCurrencyCode) + val numberFormatter = NumberFormat.getCurrencyInstance(locale).apply { + currency = formatterCurrency + } + val formatter = requireNotNull(numberFormatter as? DecimalFormat) { + Timber.e("NumberFormat is null") + return BigDecimalFormatConstants.EMPTY_BALANCE_SIGN + } + return "${formatter.positivePrefix}$fiatAmount${formatter.positiveSuffix}" + .replace(formatterCurrency.getSymbol(locale), fiatCurrencySymbol) + } + + private fun formatWithSymbol(amount: String, symbol: String) = "$amount$CURRENCY_SPACE$symbol" + private class OffsetMappingImpl( private val text: String, private val formattedText: AnnotatedString, diff --git a/core/ui/src/main/java/com/tangem/core/ui/format/bigdecimal/BigDecimalCompactFormat.kt b/core/ui/src/main/java/com/tangem/core/ui/format/bigdecimal/BigDecimalCompactFormat.kt new file mode 100644 index 0000000000..f083a63c47 --- /dev/null +++ b/core/ui/src/main/java/com/tangem/core/ui/format/bigdecimal/BigDecimalCompactFormat.kt @@ -0,0 +1,159 @@ +package com.tangem.core.ui.format.bigdecimal + +import android.icu.text.CompactDecimalFormat +import java.math.BigDecimal +import java.math.RoundingMode +import java.text.NumberFormat +import java.util.Locale + +// == Formatters == + +/** + * Formats the amount in compact format. + * "123456.6" -> "$123.457K" + * "12345.6" -> "$123.046K" + * @param threeDigitsMethod if true, will format the amount always with 3 significant digits + */ +fun BigDecimalFiatFormat.compact(threeDigitsMethod: Boolean = false): BigDecimalFormat = BigDecimalFormat { value -> + if (value < BigDecimal.ONE) { + return@BigDecimalFormat defaultAmount()(value) + } + + val rawAmount = formatCompactAmount( + amount = value, + locale = locale, + threeDigitsMethod = threeDigitsMethod, + ) + + addFiatCurrencySymbolToStringAmount( + amount = rawAmount, + fiatCurrencyCode = fiatCurrencyCode, + fiatCurrencySymbol = fiatCurrencySymbol, + locale = locale, + ) +} + +/** + * Formats the amount in compact format. + * "123456.6" -> "ETH 123.457K" + * "12345.6" -> "123.046K ETH" + * @param threeDigitsMethod if true, will format the amount always with 3 significant digits + */ +fun BigDecimalCryptoFormat.compact(threeDigitsMethod: Boolean = false): BigDecimalFormat = BigDecimalFormat { value -> + if (value < BigDecimal.ONE) { + return@BigDecimalFormat defaultAmount()(value) + } + + val rawAmount = formatCompactAmount( + amount = value, + locale = locale, + threeDigitsMethod = threeDigitsMethod, + ) + + addFiatCurrencySymbolToStringAmount( + amount = rawAmount, + fiatCurrencyCode = BigDecimalFormatConstants.usdCurrency.currencyCode, + fiatCurrencySymbol = BigDecimalFormatConstants.usdCurrency.symbol, + locale = locale, + ).replaceFiatSymbolWithCrypto( + fiatCurrencySymbol = BigDecimalFormatConstants.usdCurrency.symbol, + cryptoCurrencySymbol = symbol, + ) +} + +/** + * Formats the amount in compact format. + * ex. "123456.6" -> "123.46K", "12345.6" -> "123.05K" + * Negative amount is not supported! + */ +fun BigDecimalFormatScope.rawCompact(locale: Locale = Locale.getDefault()) = BigDecimalFormat { value -> + if (value < BigDecimal.ZERO) { + return@BigDecimalFormat value.toPlainString() + } + + formatCompactAmount( + amount = value, + locale = locale, + threeDigitsMethod = false, + ) +} + +// == Helpers == + +/** + * "123456.6" -> "123.457K" + * "12345.6" -> "123.046K" + * Negative amount is not supported + * @param threeDigitsMethod if true, will format the amount always with 3 significant digits + * @param scale the number of digits to the right of the decimal point + */ +@Suppress("MagicNumber") +private fun formatCompactAmount( + amount: BigDecimal, + locale: Locale = Locale.getDefault(), + threeDigitsMethod: Boolean = false, +): String { + if (threeDigitsMethod) { + val scaledAmount = amount.setScale(0, RoundingMode.HALF_UP) + val digitsCount = scaledAmount.toString().count() + val digitsToFormat = 6 - when (digitsCount % 3) { + 0 -> 0 + 1 -> 2 + else -> 1 + } + + val formatter = CompactDecimalFormat.getInstance( + locale, + CompactDecimalFormat.CompactStyle.SHORT, + ).apply { + minimumSignificantDigits = 4 + maximumSignificantDigits = digitsToFormat + } + + return formatter.format(scaledAmount) + } else { + val scaledAmount = amount.setScale(0, RoundingMode.HALF_UP) + val digitsCount = scaledAmount.toString().count() + val digitsToFormat = 5 - when (digitsCount % 3) { + 0 -> 0 + 1 -> 2 + else -> 1 + } + + val formatter = CompactDecimalFormat.getInstance( + locale, + CompactDecimalFormat.CompactStyle.SHORT, + ).apply { + minimumSignificantDigits = 2 + maximumSignificantDigits = digitsToFormat + } + + return formatter.format(scaledAmount) + } +} + +/** + * Adds a proper currency symbol for the provided formatted [amount] + * ex. '10.0k" -> "$10.0k", "string" -> "$string" + */ +private fun addFiatCurrencySymbolToStringAmount( + amount: String, + fiatCurrencyCode: String, + fiatCurrencySymbol: String, + locale: Locale = Locale.getDefault(), +): String { + val sampleAmount = BigDecimal.TEN + val currency = getJavaCurrencyByCode(fiatCurrencyCode) + + val formatter = NumberFormat.getCurrencyInstance(locale).apply { + maximumFractionDigits = 0 + minimumFractionDigits = 0 + this.currency = currency + } + + val formatted = formatter.format(sampleAmount) + .replace(currency.getSymbol(locale), fiatCurrencySymbol) + .replace(sampleAmount.toString(), amount) + + return formatted +} \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/format/bigdecimal/BigDecimalCryptoFormat.kt b/core/ui/src/main/java/com/tangem/core/ui/format/bigdecimal/BigDecimalCryptoFormat.kt new file mode 100644 index 0000000000..6973261be0 --- /dev/null +++ b/core/ui/src/main/java/com/tangem/core/ui/format/bigdecimal/BigDecimalCryptoFormat.kt @@ -0,0 +1,218 @@ +package com.tangem.core.ui.format.bigdecimal + +import com.tangem.core.ui.format.bigdecimal.BigDecimalFormatConstants.CAN_BE_LOWER_SIGN +import com.tangem.core.ui.format.bigdecimal.BigDecimalFormatConstants.CRYPTO_FEE_FORMAT_THRESHOLD +import com.tangem.core.ui.format.bigdecimal.BigDecimalFormatConstants.CURRENCY_SPACE +import com.tangem.core.ui.format.bigdecimal.BigDecimalFormatConstants.FORMAT_THRESHOLD +import com.tangem.domain.tokens.model.CryptoCurrency +import com.tangem.utils.extensions.isNotWhitespace +import java.math.BigDecimal +import java.math.RoundingMode +import java.text.NumberFormat +import java.util.Currency +import java.util.Locale + +open class BigDecimalCryptoFormat( + val symbol: String, + val decimals: Int, + val locale: Locale = Locale.getDefault(), +) : BigDecimalFormat { + + override fun invoke(value: BigDecimal): String = defaultAmount()(value) +} + +class BigDecimalCryptoFormatFull( + val cryptoCurrency: CryptoCurrency, + locale: Locale = Locale.getDefault(), +) : BigDecimalCryptoFormat( + symbol = cryptoCurrency.symbol, + decimals = cryptoCurrency.decimals, + locale = locale, +) { + override fun invoke(value: BigDecimal): String = defaultAmount()(value) +} + +// == Initializers == + +fun BigDecimalFormatScope.crypto( + symbol: String, + decimals: Int, + locale: Locale = Locale.getDefault(), +): BigDecimalCryptoFormat { + return BigDecimalCryptoFormat( + symbol = symbol, + decimals = decimals, + locale = locale, + ) +} + +fun BigDecimalFormatScope.crypto( + cryptoCurrency: CryptoCurrency, + locale: Locale = Locale.getDefault(), +): BigDecimalCryptoFormat { + return BigDecimalCryptoFormat( + symbol = cryptoCurrency.symbol, + decimals = cryptoCurrency.decimals, + locale = locale, + ) +} + +// == Formatters == + +fun BigDecimalCryptoFormat.defaultAmount() = BigDecimalFormat { value -> + val formatter = NumberFormat.getCurrencyInstance(locale).apply { + currency = usdCurrency + maximumFractionDigits = decimals.coerceAtMost(maximumValue = 8) + minimumFractionDigits = 2 + isGroupingUsed = true + roundingMode = RoundingMode.HALF_UP + } + + formatter.format(value) + .replaceFiatSymbolWithCrypto( + fiatCurrencySymbol = usdCurrency.getSymbol(locale), + cryptoCurrencySymbol = symbol, + ) +} + +fun BigDecimalCryptoFormat.shorted() = BigDecimalFormat { value -> + val formatter = if (value.isMoreThanThreshold()) { + NumberFormat.getCurrencyInstance(locale).apply { + currency = usdCurrency + maximumFractionDigits = 2 + minimumFractionDigits = 2 + isGroupingUsed = true + roundingMode = RoundingMode.HALF_UP + } + } else { + NumberFormat.getCurrencyInstance(locale).apply { + currency = usdCurrency + maximumFractionDigits = decimals.coerceAtMost(maximumValue = 6) + minimumFractionDigits = 2 + isGroupingUsed = true + roundingMode = RoundingMode.DOWN + } + } + + formatter.format(value) + .replaceFiatSymbolWithCrypto( + fiatCurrencySymbol = usdCurrency.getSymbol(locale), + cryptoCurrencySymbol = symbol, + ) +} + +/** + * Format for displaying crypto amounts with their original decimals. + */ +fun BigDecimalCryptoFormat.uncapped() = BigDecimalFormat { value -> + val formatter = NumberFormat.getCurrencyInstance(locale).apply { + currency = usdCurrency + maximumFractionDigits = decimals + minimumFractionDigits = 2 + isGroupingUsed = true + roundingMode = RoundingMode.HALF_UP + } + + formatter.format(value) + .replaceFiatSymbolWithCrypto( + fiatCurrencySymbol = usdCurrency.getSymbol(locale), + cryptoCurrencySymbol = symbol, + ) +} + +/** + * Format for displaying fees. + * If the fee is less than the threshold, it will be displayed as a fixed value "<0.000001 BTC", " + val formatter = NumberFormat.getCurrencyInstance(locale).apply { + currency = usdCurrency + maximumFractionDigits = decimals.coerceAtMost(maximumValue = 6) + minimumFractionDigits = 2 + isGroupingUsed = true + roundingMode = RoundingMode.HALF_UP + } + + if (value.lessThanFeeCryptoThreshold()) { + buildString { + append(CAN_BE_LOWER_SIGN) + append( + formatter + .format(CRYPTO_FEE_FORMAT_THRESHOLD) + .replaceFiatSymbolWithCrypto( + fiatCurrencySymbol = usdCurrency.getSymbol(locale), + cryptoCurrencySymbol = symbol, + addStartSpace = true, + ), + ) + } + } else { + buildString { + if (canBeLower) { + append(CAN_BE_LOWER_SIGN) + } + append( + formatter.format(value) + .replaceFiatSymbolWithCrypto( + fiatCurrencySymbol = usdCurrency.getSymbol(locale), + cryptoCurrencySymbol = symbol, + addStartSpace = canBeLower, + ), + ) + } + } +} + +// == Helpers == + +private fun BigDecimal.isMoreThanThreshold() = this > FORMAT_THRESHOLD + +private fun BigDecimal.lessThanFeeCryptoThreshold() = this > BigDecimal.ZERO && this < CRYPTO_FEE_FORMAT_THRESHOLD + +private val usdCurrency = Currency.getInstance(Locale.US) + +// Replaces fiat currency symbol with crypto currency symbol +// with respect to the position of the symbol and whitespace +internal fun String.replaceFiatSymbolWithCrypto( + fiatCurrencySymbol: String, + cryptoCurrencySymbol: String, + addStartSpace: Boolean = false, +): String { + val str = this + if (str.isEmpty()) return str + + return buildString { + when { + str.endsWith(fiatCurrencySymbol) -> { + val withoutSymbol = str.dropLast(fiatCurrencySymbol.length) + val last = withoutSymbol.lastOrNull() ?: return cryptoCurrencySymbol + + append(withoutSymbol) + + if (last.isNotWhitespace()) { + append(CURRENCY_SPACE) + } + + append(cryptoCurrencySymbol) + } + str.startsWith(fiatCurrencySymbol) -> { + if (addStartSpace) { + append(CURRENCY_SPACE) + } + + append(cryptoCurrencySymbol) + + val withoutSymbol = str.drop(fiatCurrencySymbol.length) + val first = withoutSymbol.firstOrNull() + ?: return cryptoCurrencySymbol + + if (first.isNotWhitespace()) { + append(CURRENCY_SPACE) + } + + append(withoutSymbol) + } + else -> append(str) + } + } +} \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/format/bigdecimal/BigDecimalFiatFormat.kt b/core/ui/src/main/java/com/tangem/core/ui/format/bigdecimal/BigDecimalFiatFormat.kt new file mode 100644 index 0000000000..4822754075 --- /dev/null +++ b/core/ui/src/main/java/com/tangem/core/ui/format/bigdecimal/BigDecimalFiatFormat.kt @@ -0,0 +1,144 @@ +package com.tangem.core.ui.format.bigdecimal + +import com.tangem.core.ui.format.bigdecimal.BigDecimalFormatConstants.CAN_BE_LOWER_SIGN +import com.tangem.utils.StringsSigns.TILDE_SIGN +import java.math.BigDecimal +import java.math.RoundingMode +import java.text.NumberFormat +import java.util.Locale + +open class BigDecimalFiatFormat( + val fiatCurrencyCode: String, + val fiatCurrencySymbol: String, + val locale: Locale = Locale.getDefault(), +) : BigDecimalFormat { + override fun invoke(p1: BigDecimal): String = error("") +} + +// == Initializers == + +fun BigDecimalFormatScope.fiat( + fiatCurrencyCode: String, + fiatCurrencySymbol: String, + locale: Locale = Locale.getDefault(), +): BigDecimalFiatFormat { + return BigDecimalFiatFormat( + fiatCurrencyCode = fiatCurrencyCode, + fiatCurrencySymbol = fiatCurrencySymbol, + locale = locale, + ) +} + +// == Formatters == + +/** + * Formats fiat amount with default precision. + */ +fun BigDecimalFiatFormat.defaultAmount(): BigDecimalFormat = BigDecimalFormat { value -> + val formatterCurrency = getJavaCurrencyByCode(fiatCurrencyCode) + + val formatter = NumberFormat.getCurrencyInstance(locale).apply { + currency = formatterCurrency + maximumFractionDigits = FIAT_MARKET_DEFAULT_DIGITS + minimumFractionDigits = FIAT_MARKET_DEFAULT_DIGITS + roundingMode = RoundingMode.HALF_UP + } + + if (value.isLessThanThreshold()) { + buildString { + append(CAN_BE_LOWER_SIGN) + append( + formatter.format(FIAT_FORMAT_THRESHOLD) + .replace(formatterCurrency.getSymbol(locale), fiatCurrencySymbol), + ) + } + } else { + formatter.format(value) + .replace(formatterCurrency.getSymbol(locale), fiatCurrencySymbol) + } +} + +/** + * Formats fiat amount with default precision and adds tilde sign + */ +fun BigDecimalFiatFormat.approximateAmount(): BigDecimalFormat = BigDecimalFormat { value -> + val formattedAmount = defaultAmount()(value) + + if (value.isLessThanThreshold()) { + formattedAmount + } else { + buildString { + append(TILDE_SIGN) + append(formattedAmount) + } + } +} + +/** + * Formats fiat amount with extended precision. + */ +fun BigDecimalFiatFormat.uncapped(): BigDecimalFormat = BigDecimalFormat { value -> + val formatterCurrency = getJavaCurrencyByCode(fiatCurrencyCode) + + val digits = if (value.isLessThanThreshold()) { + FIAT_MARKET_EXTENDED_DIGITS + } else { + FIAT_MARKET_DEFAULT_DIGITS + } + + val formatter = NumberFormat.getCurrencyInstance(locale).apply { + currency = formatterCurrency + maximumFractionDigits = digits + minimumFractionDigits = FIAT_MARKET_DEFAULT_DIGITS + roundingMode = RoundingMode.HALF_UP + } + + formatter.format(value) + .replace(formatterCurrency.getSymbol(locale), fiatCurrencySymbol) +} + +/** + * Formats fiat price with precision calculated based on the value. + * @see getFiatPriceAmountWithScale + */ +fun BigDecimalFiatFormat.price(): BigDecimalFormat = BigDecimalFormat { value -> + val formatterCurrency = getJavaCurrencyByCode(fiatCurrencyCode) + + val (priceAmount, finalScale) = getFiatPriceAmountWithScale(value = value) + + val formatter = NumberFormat.getCurrencyInstance(locale).apply { + currency = formatterCurrency + maximumFractionDigits = finalScale + minimumFractionDigits = FIAT_MARKET_DEFAULT_DIGITS + roundingMode = RoundingMode.HALF_UP + } + + formatter.format(priceAmount) + .replace(formatterCurrency.getSymbol(locale), fiatCurrencySymbol) +} + +// == Helpers == + +private fun BigDecimal.isLessThanThreshold() = this > BigDecimal.ZERO && this < FIAT_FORMAT_THRESHOLD + +private fun getFiatPriceAmountWithScale(value: BigDecimal): Pair { + return if (value < BigDecimal.ONE) { + val leadingZeroes = value.scale() - value.precision() + val scale = leadingZeroes + FRACTIONAL_PART_LENGTH_AFTER_LEADING_ZEROES + + val amount = value + .setScale(scale, RoundingMode.HALF_UP) + .stripTrailingZeros() + + amount to amount.scale() + } else { + value to FIAT_MARKET_DEFAULT_DIGITS + } +} + +// == Constants == + +private val FIAT_FORMAT_THRESHOLD = BigDecimal("0.01") +private const val FIAT_MARKET_DEFAULT_DIGITS = 2 +private const val FIAT_MARKET_EXTENDED_DIGITS = 6 +private const val FRACTIONAL_PART_LENGTH_AFTER_LEADING_ZEROES = 4 \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/format/bigdecimal/BigDecimalFormat.kt b/core/ui/src/main/java/com/tangem/core/ui/format/bigdecimal/BigDecimalFormat.kt new file mode 100644 index 0000000000..c685db7f1d --- /dev/null +++ b/core/ui/src/main/java/com/tangem/core/ui/format/bigdecimal/BigDecimalFormat.kt @@ -0,0 +1,29 @@ +package com.tangem.core.ui.format.bigdecimal + +import java.math.BigDecimal + +interface BigDecimalFormatScope { + companion object { val Empty = object : BigDecimalFormatScope {} } +} + +fun interface BigDecimalFormat : (BigDecimal) -> String, BigDecimalFormatScope + +inline fun BigDecimal.format(block: BigDecimalFormatScope.() -> BigDecimalFormat): String { + return BigDecimalFormatScope.Empty.block()(this) +} + +inline fun BigDecimal?.format( + fallbackString: String = BigDecimalFormatConstants.EMPTY_BALANCE_SIGN, + block: BigDecimalFormatScope.() -> BigDecimalFormat, +): String { + if (this == null) return fallbackString + return BigDecimalFormatScope.Empty.block()(this) +} + +fun BigDecimal?.format( + format: BigDecimalFormat, + fallbackString: String = BigDecimalFormatConstants.EMPTY_BALANCE_SIGN, +): String { + if (this == null) return fallbackString + return format(this) +} \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/format/bigdecimal/BigDecimalFormatConstants.kt b/core/ui/src/main/java/com/tangem/core/ui/format/bigdecimal/BigDecimalFormatConstants.kt new file mode 100644 index 0000000000..59835ab84c --- /dev/null +++ b/core/ui/src/main/java/com/tangem/core/ui/format/bigdecimal/BigDecimalFormatConstants.kt @@ -0,0 +1,20 @@ +package com.tangem.core.ui.format.bigdecimal + +import com.tangem.utils.StringsSigns.DASH_SIGN +import com.tangem.utils.StringsSigns.LOWER_SIGN +import java.math.BigDecimal +import java.util.Currency +import java.util.Locale + +object BigDecimalFormatConstants { + + const val EMPTY_BALANCE_SIGN = DASH_SIGN + const val CAN_BE_LOWER_SIGN = LOWER_SIGN + val FORMAT_THRESHOLD = BigDecimal("0.01") + + const val CURRENCY_SPACE = '\u00a0' + + val CRYPTO_FEE_FORMAT_THRESHOLD = BigDecimal("0.000001") + + val usdCurrency: Currency by lazy { Currency.getInstance(Locale.US) } +} \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/format/bigdecimal/BigDecimalPercentFormat.kt b/core/ui/src/main/java/com/tangem/core/ui/format/bigdecimal/BigDecimalPercentFormat.kt new file mode 100644 index 0000000000..b7019d1d3c --- /dev/null +++ b/core/ui/src/main/java/com/tangem/core/ui/format/bigdecimal/BigDecimalPercentFormat.kt @@ -0,0 +1,39 @@ +package com.tangem.core.ui.format.bigdecimal + +import java.math.BigDecimal +import java.math.RoundingMode +import java.text.NumberFormat +import java.util.Locale + +class BigDecimalPercentFormat( + val withoutSign: Boolean = true, + val locale: Locale = Locale.getDefault(), +) : BigDecimalFormat { + override fun invoke(value: BigDecimal): String = default()(value) +} + +// == Initializers == + +fun BigDecimalFormatScope.percent( + withoutSign: Boolean = true, + locale: Locale = Locale.getDefault(), +): BigDecimalPercentFormat { + return BigDecimalPercentFormat( + withoutSign = withoutSign, + locale = locale, + ) +} + +// == Formatters == + +private fun BigDecimalPercentFormat.default(): BigDecimalFormat = BigDecimalFormat { value -> + val formatter = NumberFormat.getPercentInstance(locale).apply { + maximumFractionDigits = 2 + minimumFractionDigits = 2 + roundingMode = RoundingMode.HALF_UP + } + + val valueToFormat = if (withoutSign) value.abs() else value + + formatter.format(valueToFormat) +} \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/format/bigdecimal/Utils.kt b/core/ui/src/main/java/com/tangem/core/ui/format/bigdecimal/Utils.kt new file mode 100644 index 0000000000..94509b276f --- /dev/null +++ b/core/ui/src/main/java/com/tangem/core/ui/format/bigdecimal/Utils.kt @@ -0,0 +1,15 @@ +package com.tangem.core.ui.format.bigdecimal + +import java.util.Currency + +fun getJavaCurrencyByCode(code: String): Currency { + return runCatching { Currency.getInstance(code) } + .getOrElse { e -> + // Currency code is not valid ISO 4217 code + if (e is IllegalArgumentException) { + BigDecimalFormatConstants.usdCurrency + } else { + throw e + } + } +} \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/utils/BigDecimalFormatter.kt b/core/ui/src/main/java/com/tangem/core/ui/utils/BigDecimalFormatter.kt index 4c44809cfa..fd6a353bb1 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/utils/BigDecimalFormatter.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/utils/BigDecimalFormatter.kt @@ -1,20 +1,17 @@ package com.tangem.core.ui.utils -import android.icu.text.CompactDecimalFormat import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.utils.StringsSigns.DASH_SIGN import com.tangem.utils.StringsSigns.LOWER_SIGN import com.tangem.utils.StringsSigns.TILDE_SIGN -import com.tangem.utils.extensions.isNotWhitespace -import timber.log.Timber import java.math.BigDecimal import java.math.RoundingMode -import java.text.DecimalFormat import java.text.NumberFormat import java.util.Currency import java.util.Locale @Suppress("LargeClass") +@Deprecated("Use BigDecimal.format") object BigDecimalFormatter { const val EMPTY_BALANCE_SIGN = DASH_SIGN @@ -30,10 +27,7 @@ object BigDecimalFormatter { private val usdCurrency = Currency.getInstance("USD") - @Deprecated( - "Use formatCryptoAmount2", - replaceWith = ReplaceWith("formatCryptoAmount2"), - ) + @Deprecated("Use BigDecimal.format") fun formatCryptoAmount( cryptoAmount: BigDecimal?, cryptoCurrency: String, @@ -58,30 +52,7 @@ object BigDecimalFormatter { } } - // Migrate to this method from formatCryptoAmount ([REDACTED_TASK_KEY]) - fun formatCryptoAmount2( - cryptoAmount: BigDecimal?, - cryptoCurrency: String, - decimals: Int, - locale: Locale = Locale.getDefault(), - ): String { - if (cryptoAmount == null) return EMPTY_BALANCE_SIGN - - val formatter = NumberFormat.getCurrencyInstance(locale).apply { - currency = usdCurrency - maximumFractionDigits = decimals.coerceAtMost(maximumValue = 8) - minimumFractionDigits = 2 - isGroupingUsed = true - roundingMode = RoundingMode.HALF_UP - } - - return formatter.format(cryptoAmount) - .replaceFiatSymbolWithCrypto( - fiatCurrencySymbol = usdCurrency.symbol, - cryptoCurrencySymbol = cryptoCurrency, - ) - } - + @Deprecated("Use BigDecimal.format") fun formatCryptoAmountShorted( cryptoAmount: BigDecimal?, cryptoCurrency: String, @@ -115,6 +86,7 @@ object BigDecimalFormatter { } } + @Deprecated("Use BigDecimal.format") fun formatCryptoAmountUncapped( cryptoAmount: BigDecimal?, cryptoCurrency: CryptoCurrency, @@ -138,6 +110,7 @@ object BigDecimalFormatter { } } + @Deprecated("Use BigDecimal.format") fun formatCryptoFeeAmount( cryptoAmount: BigDecimal?, cryptoCurrency: String, @@ -177,6 +150,7 @@ object BigDecimalFormatter { } } + @Deprecated("Use BigDecimal.format") fun formatCryptoAmount( cryptoAmount: BigDecimal?, cryptoCurrency: CryptoCurrency, @@ -185,6 +159,7 @@ object BigDecimalFormatter { return formatCryptoAmount(cryptoAmount, cryptoCurrency.symbol, cryptoCurrency.decimals, locale) } + @Deprecated("Use BigDecimal.format") fun formatFiatAmount( fiatAmount: BigDecimal?, fiatCurrencyCode: String, @@ -226,6 +201,7 @@ object BigDecimalFormatter { } } + @Deprecated("Use BigDecimal.format") fun formatFiatAmountUncapped( fiatAmount: BigDecimal?, fiatCurrencyCode: String, @@ -251,6 +227,7 @@ object BigDecimalFormatter { .replace(formatterCurrency.getSymbol(locale), fiatCurrencySymbol) } + @Deprecated("Use BigDecimal.format") fun formatFiatPriceUncapped( fiatAmount: BigDecimal?, fiatCurrencyCode: String, @@ -273,6 +250,7 @@ object BigDecimalFormatter { .replace(formatterCurrency.getSymbol(locale), fiatCurrencySymbol) } + @Deprecated("Use BigDecimal.format") fun getFiatPriceUncappedWithScale(value: BigDecimal): Pair { return if (value < BigDecimal.ONE) { val leadingZeroes = value.scale() - value.precision() @@ -288,26 +266,7 @@ object BigDecimalFormatter { } } - fun formatFiatEditableAmount( - fiatAmount: String?, - fiatCurrencyCode: String, - fiatCurrencySymbol: String, - locale: Locale = Locale.getDefault(), - ): String { - if (fiatAmount == null) return EMPTY_BALANCE_SIGN - - val formatterCurrency = getCurrency(fiatCurrencyCode) - val numberFormatter = NumberFormat.getCurrencyInstance(locale).apply { - currency = formatterCurrency - } - val formatter = requireNotNull(numberFormatter as? DecimalFormat) { - Timber.e("NumberFormat is null") - return EMPTY_BALANCE_SIGN - } - return "${formatter.positivePrefix}$fiatAmount${formatter.positiveSuffix}" - .replace(formatterCurrency.getSymbol(locale), fiatCurrencySymbol) - } - + @Deprecated("Use BigDecimal.format") fun formatPercent( percent: BigDecimal, useAbsoluteValue: Boolean, @@ -341,230 +300,6 @@ object BigDecimalFormatter { } } - /** - * Adds a proper currency sign for the provided formatted [amount] - * ex. '10.0k" -> "$10.0k", "string" -> "$string" - */ - private fun addCurrencySymbolToStringAmount( - amount: String, - fiatCurrencyCode: String, - fiatCurrencySymbol: String, - locale: Locale = Locale.getDefault(), - ): String { - val sampleAmount = BigDecimal.TEN - val currency = getCurrency(fiatCurrencyCode) - - val formatter = NumberFormat.getCurrencyInstance(locale).apply { - maximumFractionDigits = 0 - minimumFractionDigits = 0 - this.currency = currency - } - - val formatted = formatter.format(sampleAmount) - .replace(currency.getSymbol(locale), fiatCurrencySymbol) - .replace(sampleAmount.toString(), amount) - - return formatted - } - - /** - * Adds a proper currency sign for the provided formatted [amount] - * ex. '10.0k" -> "ETH 10.0k", "string" -> "ETH string" - */ - private fun addCryptoCurrencySymbolToStringAmount( - amount: String, - cryptoCurrencySymbol: String, - locale: Locale = Locale.getDefault(), - ): String { - val sampleAmount = BigDecimal.TEN - - val formatter = NumberFormat.getCurrencyInstance(locale).apply { - maximumFractionDigits = 0 - minimumFractionDigits = 0 - currency = usdCurrency - } - - val formatted = formatter.format(sampleAmount) - .replace(sampleAmount.toString(), amount) - - return formatted.replaceFiatSymbolWithCrypto( - fiatCurrencySymbol = usdCurrency.symbol, - cryptoCurrencySymbol = cryptoCurrencySymbol, - ) - } - - /** - * "123456.6" -> "$123.457K" - * "12345.6" -> "$123.046K" - * Negative amount is not supported - * @param threeDigitsMethod if true, will format the amount always with 3 significant digits - * @param scale the number of digits to the right of the decimal point - */ - @Suppress("MagicNumber") - fun formatCompactFiatAmount( - amount: BigDecimal?, - fiatCurrencyCode: String, - fiatCurrencySymbol: String, - threeDigitsMethod: Boolean = false, - scale: Int = 0, - locale: Locale = Locale.getDefault(), - ): String { - if (amount == null) return EMPTY_BALANCE_SIGN - - if (amount < BigDecimal.ONE) { - return formatFiatPriceUncapped( - fiatAmount = amount, - fiatCurrencyCode = fiatCurrencyCode, - fiatCurrencySymbol = fiatCurrencySymbol, - locale = locale, - ) - } - - val rawAmount = formatCompactAmount( - amount = amount, - locale = locale, - threeDigitsMethod = threeDigitsMethod, - scale = scale, - ) - - return addCurrencySymbolToStringAmount( - amount = rawAmount, - fiatCurrencyCode = fiatCurrencyCode, - fiatCurrencySymbol = fiatCurrencySymbol, - locale = locale, - ) - } - - /** - * "123456.6" -> "ETH 123.457K" - * "12345.6" -> "123.046K ETH" - * Negative amount is not supported - * @param threeDigitsMethod if true, will format the amount always with 3 significant digits - * @param scale the number of digits to the right of the decimal point - */ - fun formatCompactCryptoAmount( - amount: BigDecimal?, - cryptoCurrencySymbol: String, - threeDigitsMethod: Boolean = false, - decimals: Int = 0, - locale: Locale = Locale.getDefault(), - ): String { - if (amount == null) return EMPTY_BALANCE_SIGN - - if (amount < BigDecimal.ONE) { - return formatCryptoAmount2( - cryptoAmount = amount, - cryptoCurrency = cryptoCurrencySymbol, - decimals = decimals, - locale = locale, - ) - } - - val rawAmount = formatCompactAmount( - amount = amount, - locale = locale, - threeDigitsMethod = threeDigitsMethod, - scale = decimals, - ) - - return addCryptoCurrencySymbolToStringAmount( - amount = rawAmount, - cryptoCurrencySymbol = cryptoCurrencySymbol, - locale = locale, - ) - } - - /** - * "123456.6" -> "123.457K" - * "12345.6" -> "123.046K" - * Negative amount is not supported - * @param threeDigitsMethod if true, will format the amount always with 3 significant digits - * @param scale the number of digits to the right of the decimal point - */ - @Suppress("MagicNumber") - fun formatCompactAmount( - amount: BigDecimal, - locale: Locale = Locale.getDefault(), - threeDigitsMethod: Boolean = false, - scale: Int = 0, - ): String { - if (threeDigitsMethod) { - val scaledAmount = amount.setScale(scale, RoundingMode.HALF_UP) - val digitsCount = scaledAmount.toString().count() - val digitsToFormat = 6 - when (digitsCount % 3) { - 0 -> 0 - 1 -> 2 - else -> 1 - } - - val formatter = CompactDecimalFormat.getInstance( - locale, - CompactDecimalFormat.CompactStyle.SHORT, - ).apply { - minimumSignificantDigits = 4 - maximumSignificantDigits = digitsToFormat - } - - return formatter.format(amount.setScale(scale, RoundingMode.HALF_UP)) - } else { - val scaledAmount = amount.setScale(scale, RoundingMode.HALF_UP) - val digitsCount = scaledAmount.toString().count() - val digitsToFormat = 5 - when (digitsCount % 3) { - 0 -> 0 - 1 -> 2 - else -> 1 - } - - val formatter = CompactDecimalFormat.getInstance( - locale, - CompactDecimalFormat.CompactStyle.SHORT, - ).apply { - minimumSignificantDigits = 2 - maximumSignificantDigits = digitsToFormat - } - - return formatter.format(amount.setScale(scale, RoundingMode.HALF_UP)) - } - } - - // Replaces fiat currency symbol with crypto currency symbol - // with respect to the position of the symbol and whitespace - private fun String.replaceFiatSymbolWithCrypto(fiatCurrencySymbol: String, cryptoCurrencySymbol: String): String { - val str = this - if (str.isEmpty()) return str - - return buildString { - when { - str.endsWith(fiatCurrencySymbol) -> { - val withoutSymbol = str.dropLast(fiatCurrencySymbol.length) - val last = withoutSymbol.lastOrNull() ?: return cryptoCurrencySymbol - - append(withoutSymbol) - - if (last.isNotWhitespace()) { - append("\u2009") - } - - append(cryptoCurrencySymbol) - } - str.startsWith(fiatCurrencySymbol) -> { - append(cryptoCurrencySymbol) - - val withoutSymbol = str.drop(fiatCurrencySymbol.length) - val first = withoutSymbol.firstOrNull() - ?: return cryptoCurrencySymbol - - if (first.isNotWhitespace()) { - append("\u2009") - } - - append(withoutSymbol) - } - else -> append(str) - } - } - } - private fun BigDecimal.checkFiatThreshold() = this > BigDecimal.ZERO && this < FIAT_FORMAT_THRESHOLD private fun BigDecimal.checkCryptoThreshold() = this > BigDecimal.ZERO && this < CRYPTO_FEE_FORMAT_THRESHOLD diff --git a/core/ui/src/test/kotlin/com/tangem/core/ui/format/bigdecimal/BigDecimalCryptoFormatTest.kt b/core/ui/src/test/kotlin/com/tangem/core/ui/format/bigdecimal/BigDecimalCryptoFormatTest.kt new file mode 100644 index 0000000000..cc6fa5e40e --- /dev/null +++ b/core/ui/src/test/kotlin/com/tangem/core/ui/format/bigdecimal/BigDecimalCryptoFormatTest.kt @@ -0,0 +1,373 @@ +package com.tangem.core.ui.format.bigdecimal + +import com.google.common.truth.Truth +import org.junit.Test +import java.math.BigDecimal +import java.util.Locale + +internal class BigDecimalCryptoFormatTest { + + private val testLocale = Locale.US + private val testLocale2 = Locale.GERMANY + private val symbol = "BTC" + + // === defaultAmount() === + + @Test + fun `defaultAmount (usually used as a user balance)`() { + val testValue = BigDecimal("0.123456789999") + + val formatted = testValue.format { + crypto( + symbol = symbol, + decimals = 8, + locale = testLocale, + ).defaultAmount() + } + + Truth.assertThat(formatted) + .isEqualTo("0.12345679".addSymbolWithSpaceLeft(symbol)) + } + + @Test + fun `defaultAmount (usually used as a user balance) alter locale`() { + val testValue = BigDecimal("0.123456789999") + + val formatted = testValue.format { + crypto( + symbol = symbol, + decimals = 8, + locale = testLocale2, + ).defaultAmount() + } + + Truth.assertThat(formatted) + .isEqualTo("0,12345679".addSymbolWithSpaceRight(symbol)) + } + + @Test + fun `defaultAmount decimals more than 8`() { + val testValue = BigDecimal("0.123456789999") + + val formatted = testValue.format { + crypto( + symbol = symbol, + decimals = 10, + locale = testLocale, + ).defaultAmount() + } + + Truth.assertThat(formatted) + .isEqualTo("0.12345679".addSymbolWithSpaceLeft(symbol)) + } + + @Test + fun `defaultAmount decimals more than 8 (short value)`() { + val testValue = BigDecimal("0.12345") + + val formatted = testValue.format { + crypto( + symbol = symbol, + decimals = 10, + locale = testLocale, + ).defaultAmount() + } + + Truth.assertThat(formatted) + .isEqualTo("0.12345".addSymbolWithSpaceLeft(symbol)) + } + + @Test + fun `defaultAmount decimals minimal (short value)`() { + val testValue = BigDecimal("0.12345") + + val formatted = testValue.format { + crypto( + symbol = symbol, + decimals = 2, + locale = testLocale, + ).defaultAmount() + } + + Truth.assertThat(formatted) + .isEqualTo("0.12".addSymbolWithSpaceLeft(symbol)) + } + + @Test + fun `defaultAmount less than 2 decimals`() { + val testValue = BigDecimal("0.12345") + + val formatted = testValue.format { + crypto( + symbol = symbol, + decimals = 0, + locale = testLocale, + ).defaultAmount() + } + + Truth.assertThat(formatted) + .isEqualTo("0.12".addSymbolWithSpaceLeft(symbol)) + } + + @Test + fun `defaultAmount grouping`() { + val testValue = BigDecimal("12345678.11") + + val formatted = testValue.format { + crypto( + symbol = symbol, + decimals = 0, + locale = testLocale, + ).defaultAmount() + } + + Truth.assertThat(formatted) + .isEqualTo("12,345,678.11".addSymbolWithSpaceLeft(symbol)) + } + + // === shorted() === + + @Test + fun `shorted amount smoke`() { + val testValue = BigDecimal("50000.126123") + + val formatted = testValue.format { + crypto( + symbol = symbol, + decimals = 8, + locale = testLocale, + ).shorted() + } + + Truth.assertThat(formatted) + .isEqualTo("50,000.13".addSymbolWithSpaceLeft(symbol)) + } + + @Test + fun `shorted amount decimals less than 2 grouping`() { + val testValue = BigDecimal("50000.126123") + + val formatted = testValue.format { + crypto( + symbol = symbol, + decimals = 1, + locale = testLocale, + ).shorted() + } + + Truth.assertThat(formatted) + .isEqualTo("50,000.13".addSymbolWithSpaceLeft(symbol)) + } + + @Test + fun `shorted amount less than threshold`() { + val testValue = BigDecimal("0.0034567899") + + val formatted = testValue.format { + crypto( + symbol = symbol, + decimals = 4, + locale = testLocale, + ).shorted() + } + + Truth.assertThat(formatted) + .isEqualTo("0.0034".addSymbolWithSpaceLeft(symbol)) + } + + @Test + fun `shorted amount less than threshold, more decimals`() { + val testValue = BigDecimal("0.00345678") + + val formatted = testValue.format { + crypto( + symbol = symbol, + decimals = 8, + locale = testLocale, + ).shorted() + } + + Truth.assertThat(formatted) + .isEqualTo("0.003456".addSymbolWithSpaceLeft(symbol)) + } + + @Test + fun `shorted amount diff locale half up`() { + val testValue = BigDecimal("50000.126123") + + val formatted = testValue.format { + crypto( + symbol = symbol, + decimals = 8, + locale = testLocale2, + ).shorted() + } + + Truth.assertThat(formatted) + .isEqualTo("50.000,13".addSymbolWithSpaceRight(symbol)) + } + + // === uncapped() === + + @Test + fun `uncapped amount`() { + val testValue = BigDecimal("50000.123412341234") + + val formatted = testValue.format { + crypto( + symbol = symbol, + decimals = 10, + locale = testLocale, + ).uncapped() + } + + Truth.assertThat(formatted) + .isEqualTo("50,000.1234123412".addSymbolWithSpaceLeft(symbol)) + } + + @Test + fun `uncapped amount diff locale`() { + val testValue = BigDecimal("50000.123412341234") + + val formatted = testValue.format { + crypto( + symbol = symbol, + decimals = 10, + locale = testLocale2, + ).uncapped() + } + + Truth.assertThat(formatted) + .isEqualTo("50.000,1234123412".addSymbolWithSpaceRight(symbol)) + } + + @Test + fun `uncapped amount half up`() { + val testValue = BigDecimal("50000.12341234125") + + val formatted = testValue.format { + crypto( + symbol = symbol, + decimals = 10, + locale = testLocale2, + ).uncapped() + } + + Truth.assertThat(formatted) + .isEqualTo("50.000,1234123413".addSymbolWithSpaceRight(symbol)) + } + + @Test + fun `uncapped amount min decimals`() { + val testValue = BigDecimal("50000.12341234125") + + val formatted = testValue.format { + crypto( + symbol = symbol, + decimals = 1, + locale = testLocale2, + ).uncapped() + } + + Truth.assertThat(formatted) + .isEqualTo("50.000,12".addSymbolWithSpaceRight(symbol)) + } + + // === fee === + + @Test + fun `fee amount`() { + val testValue = BigDecimal("0.000123412341234") + + val formatted = testValue.format { + crypto( + symbol = symbol, + decimals = 10, + locale = testLocale, + ).fee() + } + + Truth.assertThat(formatted) + .isEqualTo("0.000123".addSymbolWithSpaceLeft(symbol)) + } + + @Test + fun `fee amount diff locale`() { + val testValue = BigDecimal("0.000123412341234") + + val formatted = testValue.format { + crypto( + symbol = symbol, + decimals = 10, + locale = testLocale2, + ).fee() + } + + Truth.assertThat(formatted) + .isEqualTo("0,000123".addSymbolWithSpaceRight(symbol)) + } + + @Test + fun `fee amount canBeLower true`() { + val testValue = BigDecimal("0.000123412341234") + + val formatted = testValue.format { + crypto( + symbol = symbol, + decimals = 10, + locale = testLocale, + ).fee(canBeLower = true) + } + + Truth.assertThat(formatted) + .isEqualTo("<" + CURRENCY_SPACE_FOR_TESTS + "0.000123".addSymbolWithSpaceLeft(symbol)) + } + + @Test + fun `fee amount canBeLower true (diff locale)`() { + val testValue = BigDecimal("0.000123412341234") + + val formatted = testValue.format { + crypto( + symbol = symbol, + decimals = 10, + locale = testLocale2, + ).fee(canBeLower = true) + } + + Truth.assertThat(formatted) + .isEqualTo("<" + "0,000123".addSymbolWithSpaceRight(symbol)) + } + + @Test + fun `fee amount lee than threshold`() { + val testValue = BigDecimal("0.0000001234") + + val formatted = testValue.format { + crypto( + symbol = symbol, + decimals = 10, + locale = testLocale, + ).fee() + } + + Truth.assertThat(formatted) + .isEqualTo("<" + CURRENCY_SPACE_FOR_TESTS + "0.000001".addSymbolWithSpaceLeft(symbol)) + } + + @Test + fun `fee amount min decimals half up`() { + val testValue = BigDecimal("0.125412341234") + + val formatted = testValue.format { + crypto( + symbol = symbol, + decimals = 1, + locale = testLocale, + ).fee() + } + + Truth.assertThat(formatted) + .isEqualTo("0.13".addSymbolWithSpaceLeft(symbol)) + } +} \ No newline at end of file diff --git a/core/ui/src/test/kotlin/com/tangem/core/ui/format/bigdecimal/BigDecimalFiatFormatTest.kt b/core/ui/src/test/kotlin/com/tangem/core/ui/format/bigdecimal/BigDecimalFiatFormatTest.kt new file mode 100644 index 0000000000..d5c2cb310e --- /dev/null +++ b/core/ui/src/test/kotlin/com/tangem/core/ui/format/bigdecimal/BigDecimalFiatFormatTest.kt @@ -0,0 +1,297 @@ +package com.tangem.core.ui.format.bigdecimal + +import com.google.common.truth.Truth +import org.junit.Test +import java.math.BigDecimal +import java.util.Locale + +internal class BigDecimalFiatFormatTest { + + val testLocale = Locale.US + val testLocale2 = Locale.GERMANY + + val usdCurrencyCode = "USD" + val usdSymbol = "$" + + private fun String.addUsdSymbolLeft() = usdSymbol + this + + // === defaultAmount() === + + @Test + fun `defaultAmount smoke`() { + val testValue = BigDecimal("1234.1234") + + val formatted = testValue.format { + fiat( + fiatCurrencyCode = usdCurrencyCode, + fiatCurrencySymbol = usdSymbol, + locale = testLocale, + ).defaultAmount() + } + + Truth.assertThat(formatted) + .isEqualTo("1,234.12".addUsdSymbolLeft()) + } + + @Test + fun `defaultAmount half up`() { + val testValue = BigDecimal("1234.125") + + val formatted = testValue.format { + fiat( + fiatCurrencyCode = usdCurrencyCode, + fiatCurrencySymbol = usdSymbol, + locale = testLocale, + ).defaultAmount() + } + + Truth.assertThat(formatted) + .isEqualTo("1,234.13".addUsdSymbolLeft()) + } + + @Test + fun `defaultAmount diff locale`() { + val testValue = BigDecimal("1234.1234") + + val formatted = testValue.format { + fiat( + fiatCurrencyCode = usdCurrencyCode, + fiatCurrencySymbol = usdSymbol, + locale = testLocale2, + ).defaultAmount() + } + + Truth.assertThat(formatted) + .isEqualTo("1.234,12".addSymbolWithSpaceRight(usdSymbol)) + } + + @Test + fun `defaultAmount less threshold`() { + val testValue = BigDecimal("0.002234") + + val formatted = testValue.format { + fiat( + fiatCurrencyCode = usdCurrencyCode, + fiatCurrencySymbol = usdSymbol, + locale = testLocale, + ).defaultAmount() + } + + Truth.assertThat(formatted) + .isEqualTo("<" + "0.01".addUsdSymbolLeft()) + } + + @Test + fun `defaultAmount less threshold diff locale`() { + val testValue = BigDecimal("0.002234") + + val formatted = testValue.format { + fiat( + fiatCurrencyCode = usdCurrencyCode, + fiatCurrencySymbol = usdSymbol, + locale = testLocale2, + ).defaultAmount() + } + + Truth.assertThat(formatted) + .isEqualTo("<" + "0,01".addSymbolWithSpaceRight(usdSymbol)) + } + + // === approximateAmount() === + + @Test + fun `approximateAmount smoke`() { + val testValue = BigDecimal("1234.1234") + + val formatted = testValue.format { + fiat( + fiatCurrencyCode = usdCurrencyCode, + fiatCurrencySymbol = usdSymbol, + locale = testLocale, + ).approximateAmount() + } + + Truth.assertThat(formatted) + .isEqualTo("~" + "1,234.12".addUsdSymbolLeft()) + } + + @Test + fun `approximateAmount half up`() { + val testValue = BigDecimal("1234.125") + + val formatted = testValue.format { + fiat( + fiatCurrencyCode = usdCurrencyCode, + fiatCurrencySymbol = usdSymbol, + locale = testLocale, + ).approximateAmount() + } + + Truth.assertThat(formatted) + .isEqualTo("~" + "1,234.13".addUsdSymbolLeft()) + } + + @Test + fun `approximateAmount diff locale`() { + val testValue = BigDecimal("1234.1234") + + val formatted = testValue.format { + fiat( + fiatCurrencyCode = usdCurrencyCode, + fiatCurrencySymbol = usdSymbol, + locale = testLocale2, + ).approximateAmount() + } + + Truth.assertThat(formatted) + .isEqualTo("~" + "1.234,12".addSymbolWithSpaceRight(usdSymbol)) + } + + @Test + fun `approximateAmount less threshold`() { + val testValue = BigDecimal("0.002234") + + val formatted = testValue.format { + fiat( + fiatCurrencyCode = usdCurrencyCode, + fiatCurrencySymbol = usdSymbol, + locale = testLocale, + ).approximateAmount() + } + + Truth.assertThat(formatted) + .isEqualTo("<" + "0.01".addUsdSymbolLeft()) + } + + // === uncapped() === + + @Test + fun `uncapped smoke`() { + val testValue = BigDecimal("1234.1234") + + val formatted = testValue.format { + fiat( + fiatCurrencyCode = usdCurrencyCode, + fiatCurrencySymbol = usdSymbol, + locale = testLocale, + ).uncapped() + } + + Truth.assertThat(formatted) + .isEqualTo("1,234.12".addUsdSymbolLeft()) + } + + @Test + fun `uncapped less threshold`() { + val testValue = BigDecimal("0.00121") + + val formatted = testValue.format { + fiat( + fiatCurrencyCode = usdCurrencyCode, + fiatCurrencySymbol = usdSymbol, + locale = testLocale, + ).uncapped() + } + + Truth.assertThat(formatted) + .isEqualTo("0.00121".addUsdSymbolLeft()) + } + + @Test + fun `uncapped decimals overflow`() { + val testValue = BigDecimal("0.00123412341234") + + val formatted = testValue.format { + fiat( + fiatCurrencyCode = usdCurrencyCode, + fiatCurrencySymbol = usdSymbol, + locale = testLocale, + ).uncapped() + } + + Truth.assertThat(formatted) + .isEqualTo("0.001234".addUsdSymbolLeft()) + } + + // === price() === + + @Test + fun `price smoke`() { + val testValue = BigDecimal("1234.1234") + + val formatted = testValue.format { + fiat( + fiatCurrencyCode = usdCurrencyCode, + fiatCurrencySymbol = usdSymbol, + locale = testLocale, + ).price() + } + + Truth.assertThat(formatted) + .isEqualTo("1,234.12".addUsdSymbolLeft()) + } + + @Test + fun `price diff locale`() { + val testValue = BigDecimal("1234.1234") + + val formatted = testValue.format { + fiat( + fiatCurrencyCode = usdCurrencyCode, + fiatCurrencySymbol = usdSymbol, + locale = testLocale2, + ).price() + } + + Truth.assertThat(formatted) + .isEqualTo("1.234,12".addSymbolWithSpaceRight(usdSymbol)) + } + + @Test + fun `price less threshold`() { + val testValue = BigDecimal("0.99987") + + val formatted = testValue.format { + fiat( + fiatCurrencyCode = usdCurrencyCode, + fiatCurrencySymbol = usdSymbol, + locale = testLocale, + ).price() + } + + Truth.assertThat(formatted) + .isEqualTo("0.9999".addUsdSymbolLeft()) + } + + @Test + fun `price less threshold more decimals strip zeros`() { + val testValue = BigDecimal("0.0000123000") + + val formatted = testValue.format { + fiat( + fiatCurrencyCode = usdCurrencyCode, + fiatCurrencySymbol = usdSymbol, + locale = testLocale, + ).price() + } + + Truth.assertThat(formatted) + .isEqualTo("0.0000123".addUsdSymbolLeft()) + } + + @Test + fun `price less threshold too much decimals strip zeros`() { + val testValue = BigDecimal("0.000000000000000000001230001234000") + + val formatted = testValue.format { + fiat( + fiatCurrencyCode = usdCurrencyCode, + fiatCurrencySymbol = usdSymbol, + locale = testLocale, + ).price() + } + + Truth.assertThat(formatted) + .isEqualTo("0.00000000000000000000123".addUsdSymbolLeft()) + } +} \ No newline at end of file diff --git a/core/ui/src/test/kotlin/com/tangem/core/ui/format/bigdecimal/BigDecimalFormatTest.kt b/core/ui/src/test/kotlin/com/tangem/core/ui/format/bigdecimal/BigDecimalFormatTest.kt new file mode 100644 index 0000000000..63061a9116 --- /dev/null +++ b/core/ui/src/test/kotlin/com/tangem/core/ui/format/bigdecimal/BigDecimalFormatTest.kt @@ -0,0 +1,29 @@ +package com.tangem.core.ui.format.bigdecimal + +import com.google.common.truth.Truth +import org.junit.Test +import java.math.BigDecimal + +internal class BigDecimalFormatTest { + + @Test + fun smoke() { + val value = BigDecimal("1234") + val bgformat = BigDecimalFormat { bg -> + bg.toPlainString() + "!" + } + val expected = "1234!" + + Truth.assertThat( + value.format(bgformat), + ).isEqualTo(expected) + + Truth.assertThat( + value.format { bgformat }, + ).isEqualTo(expected) + + Truth.assertThat( + null.format(fallbackString = "!") { bgformat }, + ).isEqualTo("!") + } +} \ No newline at end of file diff --git a/core/ui/src/test/kotlin/com/tangem/core/ui/format/bigdecimal/BigDecimalPercentFormatTest.kt b/core/ui/src/test/kotlin/com/tangem/core/ui/format/bigdecimal/BigDecimalPercentFormatTest.kt new file mode 100644 index 0000000000..3d3be8c25f --- /dev/null +++ b/core/ui/src/test/kotlin/com/tangem/core/ui/format/bigdecimal/BigDecimalPercentFormatTest.kt @@ -0,0 +1,70 @@ +package com.tangem.core.ui.format.bigdecimal + +import com.google.common.truth.Truth +import org.junit.Test +import java.math.BigDecimal +import java.util.Locale + +internal class BigDecimalPercentFormatTest { + + val testLocale = Locale.US + val testLocale2 = Locale.GERMANY + + @Test + fun smoke() { + val value = BigDecimal("00.34") + + val formatted = value.format { + percent(locale = testLocale) + } + + Truth.assertThat(formatted).isEqualTo("34.00%") + } + + @Test + fun negative() { + val value = BigDecimal("00.34").negate() + + val formatted = value.format { + percent(locale = testLocale) + } + + Truth.assertThat(formatted).isEqualTo("34.00%") + } + + @Test + fun `negative with sign`() { + val value = BigDecimal("00.34").negate() + + val formatted = value.format { + percent( + withoutSign = false, + locale = testLocale, + ) + } + + Truth.assertThat(formatted).isEqualTo("-34.00%") + } + + @Test + fun `default more decimals half up`() { + val value = BigDecimal("00.345678").negate() + + val formatted = value.format { + percent(locale = testLocale) + } + + Truth.assertThat(formatted).isEqualTo("34.57%") + } + + @Test + fun `default diff locale`() { + val value = BigDecimal("00.345678").negate() + + val formatted = value.format { + percent(locale = testLocale2) + } + + Truth.assertThat(formatted).isEqualTo("34,57".addSymbolWithSpaceRight("%")) + } +} \ No newline at end of file diff --git a/core/ui/src/test/kotlin/com/tangem/core/ui/format/bigdecimal/TestUtils.kt b/core/ui/src/test/kotlin/com/tangem/core/ui/format/bigdecimal/TestUtils.kt new file mode 100644 index 0000000000..436873335d --- /dev/null +++ b/core/ui/src/test/kotlin/com/tangem/core/ui/format/bigdecimal/TestUtils.kt @@ -0,0 +1,7 @@ +package com.tangem.core.ui.format.bigdecimal + +internal const val CURRENCY_SPACE_FOR_TESTS = '\u00a0' + +internal fun String.addSymbolWithSpaceRight(symbol: String): String = "$this$CURRENCY_SPACE_FOR_TESTS$symbol" + +internal fun String.addSymbolWithSpaceLeft(symbol: String): String = "$symbol$CURRENCY_SPACE_FOR_TESTS$this" \ No newline at end of file diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/model/converters/InsightsConverter.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/model/converters/InsightsConverter.kt index fb93bd86a2..65273ee0bb 100644 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/model/converters/InsightsConverter.kt +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/model/converters/InsightsConverter.kt @@ -3,7 +3,10 @@ package com.tangem.features.markets.details.impl.model.converters import androidx.compose.runtime.Stable import com.tangem.core.ui.extensions.resourceReference import com.tangem.core.ui.extensions.wrappedList -import com.tangem.core.ui.utils.BigDecimalFormatter +import com.tangem.core.ui.format.bigdecimal.compact +import com.tangem.core.ui.format.bigdecimal.fiat +import com.tangem.core.ui.format.bigdecimal.format +import com.tangem.core.ui.format.bigdecimal.rawCompact import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.markets.PriceChangeInterval import com.tangem.domain.markets.TokenMarketInfo @@ -142,14 +145,17 @@ internal class InsightsConverter( private fun BigDecimal.convertChange(isFiatValue: Boolean = false): String { val value = if (isFiatValue) { - val currency = appCurrency() - BigDecimalFormatter.formatCompactFiatAmount( - amount = this.abs(), - fiatCurrencyCode = currency.code, - fiatCurrencySymbol = currency.symbol, - ) + this.abs().format { + val currency = appCurrency() + fiat( + fiatCurrencyCode = currency.code, + fiatCurrencySymbol = currency.symbol, + ).compact() + } } else { - BigDecimalFormatter.formatCompactAmount(amount = this.abs()) + this.abs().format { + rawCompact() + } } return when { diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/model/converters/MetricsConverter.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/model/converters/MetricsConverter.kt index 48b3217b3d..c91039839b 100644 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/model/converters/MetricsConverter.kt +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/model/converters/MetricsConverter.kt @@ -2,7 +2,10 @@ package com.tangem.features.markets.details.impl.model.converters import androidx.compose.runtime.Stable import com.tangem.core.ui.extensions.resourceReference -import com.tangem.core.ui.utils.BigDecimalFormatter +import com.tangem.core.ui.format.bigdecimal.compact +import com.tangem.core.ui.format.bigdecimal.crypto +import com.tangem.core.ui.format.bigdecimal.fiat +import com.tangem.core.ui.format.bigdecimal.format import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.markets.TokenMarketInfo import com.tangem.features.markets.details.impl.ui.state.InfoBottomSheetContent @@ -129,18 +132,21 @@ internal class MetricsConverter( if (this == null) return StringsSigns.DASH_SIGN return if (crypto) { - BigDecimalFormatter.formatCompactCryptoAmount( - amount = this, - cryptoCurrencySymbol = tokenSymbol, - ) + format { + crypto( + symbol = tokenSymbol, + decimals = 2, + ).compact() + } } else { val currency = appCurrency() - BigDecimalFormatter.formatCompactFiatAmount( - amount = this, - fiatCurrencyCode = currency.code, - fiatCurrencySymbol = currency.symbol, - ) + format { + fiat( + fiatCurrencyCode = currency.code, + fiatCurrencySymbol = currency.symbol, + ).compact() + } } } } \ No newline at end of file diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/model/converters/MarketsTokenItemConverter.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/model/converters/MarketsTokenItemConverter.kt index b59acbc16d..f4d62e02de 100644 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/model/converters/MarketsTokenItemConverter.kt +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/model/converters/MarketsTokenItemConverter.kt @@ -5,6 +5,9 @@ import com.tangem.common.ui.charts.state.MarketChartRawData import com.tangem.common.ui.charts.state.converter.PriceAndTimePointValuesConverter import com.tangem.common.ui.charts.state.sorted import com.tangem.core.ui.components.marketprice.PriceChangeType +import com.tangem.core.ui.format.bigdecimal.compact +import com.tangem.core.ui.format.bigdecimal.fiat +import com.tangem.core.ui.format.bigdecimal.format import com.tangem.core.ui.utils.BigDecimalFormatter import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.markets.TokenMarket @@ -71,12 +74,14 @@ internal class MarketsTokenItemConverter( private fun TokenMarket.getMarketCap(): String? { val value = marketCap?.takeIf { marketCap != BigDecimal.ZERO } ?: return null - return BigDecimalFormatter.formatCompactFiatAmount( - amount = value, - fiatCurrencyCode = appCurrency.code, - fiatCurrencySymbol = appCurrency.symbol, - threeDigitsMethod = true, - ) + return value.format { + fiat( + fiatCurrencyCode = appCurrency.code, + fiatCurrencySymbol = appCurrency.symbol, + ).compact( + threeDigitsMethod = true, + ) + } } private fun TokenMarket.getCurrentPrice(prev: TokenMarket? = null): MarketsListItemUM.Price { From 29915a82242dc1681c11cfd17bafc756e12ccd43 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 15 Oct 2024 20:22:54 +0500 Subject: [PATCH 002/106] Updated on 2026-08-14 --- core/res/src/main/res/values/strings.xml | 1 + .../data/staking/DefaultStakingRepository.kt | 18 +- .../domain/staking/model/stakekit/Yield.kt | 7 +- .../action/StakingActionCommonType.kt | 14 +- .../analytics/utils/StakingAnalyticSender.kt | 19 +- .../state/StakingStateController.kt | 9 +- .../presentation/state/StakingStateRouter.kt | 28 +- .../impl/presentation/state/StakingUiState.kt | 24 +- .../impl/presentation/state/ValidatorState.kt | 34 --- .../helpers/StakingFeeTransactionLoader.kt | 16 +- .../state/helpers/StakingTransactionSender.kt | 21 +- .../ConfirmationStatePreviewData.kt | 49 ---- .../previewdata/ValidatorStatePreviewData.kt | 60 +++++ .../state/stub/StakingClickIntentsStub.kt | 14 +- .../ActionTypeActiveStakeTransformer.kt | 33 --- .../AddStakingNotificationsTransformer.kt | 21 +- .../SetActionToExecuteTransformer.kt | 25 -- .../SetButtonsStateTransformer.kt | 33 +-- .../SetConfirmationStateAssentTransformer.kt | 10 - ...etConfirmationStateCompletedTransformer.kt | 7 +- .../SetConfirmationStateEmptyTransformer.kt | 16 ++ ...tConfirmationStateInProgressTransformer.kt | 1 - .../SetConfirmationStateInitTransformer.kt | 70 +++++ .../SetConfirmationStateLoadingTransformer.kt | 29 +- ...ConfirmationStateResetAssentTransformer.kt | 1 - .../SetInitialDataStateTransformer.kt | 25 +- .../state/transformers/SetTitleTransformer.kt | 10 +- .../AmountRequirementStateTransformer.kt | 2 +- ...firmationStateAssentApprovalTransformer.kt | 1 - .../ShowApprovalBottomSheetTransformer.kt | 3 +- .../ValidatorSelectChangeTransformer.kt | 40 ++- .../state/utils/StakingPendingActionUtils.kt | 39 ++- .../ui/StakingConfirmationContent.kt | 21 +- .../impl/presentation/ui/StakingScreen.kt | 26 +- .../ui/StakingValidatorListContent.kt | 16 +- .../presentation/ui/block/ValidatorBlock.kt | 39 +-- .../viewmodel/StakingClickIntents.kt | 14 +- .../viewmodel/StakingViewModel.kt | 254 +++++++++--------- .../com/tangem/lib/crypto/BlockchainUtils.kt | 5 + 39 files changed, 531 insertions(+), 524 deletions(-) delete mode 100644 features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/ValidatorState.kt create mode 100644 features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/previewdata/ValidatorStatePreviewData.kt delete mode 100644 features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/ActionTypeActiveStakeTransformer.kt delete mode 100644 features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetActionToExecuteTransformer.kt create mode 100644 features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetConfirmationStateEmptyTransformer.kt create mode 100644 features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetConfirmationStateInitTransformer.kt diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml index 46365c5cc0..a0beef5668 100644 --- a/core/res/src/main/res/values/strings.xml +++ b/core/res/src/main/res/values/strings.xml @@ -735,6 +735,7 @@ Staking is currently unavailable due to network conditions. Please try again later. Staking in the %1$s network with a new validator will automatically transfer all previously staked funds to this validator Reinvests your earned rewards in your staked amount, increasing potential earnings. + Restake lets you move your funds from one validator to another without the need to unstake Unlock your money to withdraw it from staking process. Unlocking takes %s. Your funds will be available for use after the 21-day unbonding period. Reward will be withdrawn along with your unstaking funds. Your funds will be available for use after the %s unbonding period. diff --git a/data/staking/src/main/java/com/tangem/data/staking/DefaultStakingRepository.kt b/data/staking/src/main/java/com/tangem/data/staking/DefaultStakingRepository.kt index c3161be225..729477a5cc 100644 --- a/data/staking/src/main/java/com/tangem/data/staking/DefaultStakingRepository.kt +++ b/data/staking/src/main/java/com/tangem/data/staking/DefaultStakingRepository.kt @@ -151,7 +151,7 @@ internal class DefaultStakingRepository( val yield = getYield(cryptoCurrencyId, symbol) StakingEntryInfo( - apr = requireNotNull(yield.validators.maxByOrNull { it.apr.orZero() }?.apr), + apr = requireNotNull(yield.preferredValidators.maxByOrNull { it.apr.orZero() }?.apr), rewardSchedule = yield.metadata.rewardSchedule, tokenSymbol = yield.token.symbol, ) @@ -207,23 +207,21 @@ internal class DefaultStakingRepository( ): StakingAction { return withContext(dispatchers.io) { val response = when (params.actionCommonType) { - StakingActionCommonType.ENTER -> stakeKitApi.createEnterAction( + StakingActionCommonType.Enter -> stakeKitApi.createEnterAction( createActionRequestBody( userWalletId, network, params, ), ) - StakingActionCommonType.EXIT -> stakeKitApi.createExitAction( + StakingActionCommonType.Exit -> stakeKitApi.createExitAction( createActionRequestBody( userWalletId, network, params, ), ) - StakingActionCommonType.PENDING_OTHER, - StakingActionCommonType.PENDING_REWARDS, - -> stakeKitApi.createPendingAction( + is StakingActionCommonType.Pending -> stakeKitApi.createPendingAction( createPendingActionRequestBody(params), ) } @@ -239,23 +237,21 @@ internal class DefaultStakingRepository( ): StakingGasEstimate { return withContext(dispatchers.io) { val gasEstimateDTO = when (params.actionCommonType) { - StakingActionCommonType.ENTER -> stakeKitApi.estimateGasOnEnter( + StakingActionCommonType.Enter -> stakeKitApi.estimateGasOnEnter( createActionRequestBody( userWalletId, network, params, ), ) - StakingActionCommonType.EXIT -> stakeKitApi.estimateGasOnExit( + StakingActionCommonType.Exit -> stakeKitApi.estimateGasOnExit( createActionRequestBody( userWalletId, network, params, ), ) - StakingActionCommonType.PENDING_REWARDS, - StakingActionCommonType.PENDING_OTHER, - -> stakeKitApi.estimateGasOnPending( + is StakingActionCommonType.Pending -> stakeKitApi.estimateGasOnPending( createPendingActionRequestBody(params), ) } diff --git a/domain/staking/models/src/main/kotlin/com/tangem/domain/staking/model/stakekit/Yield.kt b/domain/staking/models/src/main/kotlin/com/tangem/domain/staking/model/stakekit/Yield.kt index a2a0a6b7fe..9f201fb81e 100644 --- a/domain/staking/models/src/main/kotlin/com/tangem/domain/staking/model/stakekit/Yield.kt +++ b/domain/staking/models/src/main/kotlin/com/tangem/domain/staking/model/stakekit/Yield.kt @@ -18,6 +18,11 @@ data class Yield( val isAvailable: Boolean, ) { + val preferredValidators: List + get() = validators.filter { it.preferred } + + fun getCurrentToken(rawCurrencyId: String?) = tokens.firstOrNull { rawCurrencyId == it.coinGeckoId } ?: token + @Serializable data class Status( val enter: Boolean, @@ -130,8 +135,6 @@ data class Yield( APR, // simple rate UNKNOWN, } - - fun getCurrentToken(rawCurrencyId: String?) = tokens.firstOrNull { rawCurrencyId == it.coinGeckoId } ?: token } @Serializable diff --git a/domain/staking/models/src/main/kotlin/com/tangem/domain/staking/model/stakekit/action/StakingActionCommonType.kt b/domain/staking/models/src/main/kotlin/com/tangem/domain/staking/model/stakekit/action/StakingActionCommonType.kt index 5a59ba211c..0e2d7abe32 100644 --- a/domain/staking/models/src/main/kotlin/com/tangem/domain/staking/model/stakekit/action/StakingActionCommonType.kt +++ b/domain/staking/models/src/main/kotlin/com/tangem/domain/staking/model/stakekit/action/StakingActionCommonType.kt @@ -1,8 +1,12 @@ package com.tangem.domain.staking.model.stakekit.action -enum class StakingActionCommonType { - ENTER, - EXIT, - PENDING_REWARDS, - PENDING_OTHER, +sealed class StakingActionCommonType { + + data object Enter : StakingActionCommonType() + data object Exit : StakingActionCommonType() + sealed class Pending : StakingActionCommonType() { + data object Restake : Pending() + data object Rewards : Pending() + data object Other : Pending() + } } \ No newline at end of file diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/analytics/utils/StakingAnalyticSender.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/analytics/utils/StakingAnalyticSender.kt index 0b3f4fcd35..7fcb116702 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/analytics/utils/StakingAnalyticSender.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/analytics/utils/StakingAnalyticSender.kt @@ -33,10 +33,10 @@ internal class StakingAnalyticSender( fun confirmationScreen(value: StakingUiState) { val confirmationState = value.confirmationState as? StakingStates.ConfirmationState.Data - val validatorState = confirmationState?.validatorState as? ValidatorState.Content + val validatorState = value.validatorState as? StakingStates.ValidatorState.Data val validatorName = validatorState?.chosenValidator?.name ?: return - if (confirmationState.innerState == InnerConfirmationStakingState.COMPLETED) return + if (confirmationState?.innerState == InnerConfirmationStakingState.COMPLETED) return analyticsEventHandler.send( StakingAnalyticsEvents.ConfirmationScreenOpened( @@ -55,6 +55,7 @@ internal class StakingAnalyticSender( StakingStep.Amount -> StakeScreenSource.Amount StakingStep.Confirmation -> StakeScreenSource.Confirmation StakingStep.Validators, + StakingStep.RestakeValidator, StakingStep.RewardsValidators, -> StakeScreenSource.Validators }, @@ -78,8 +79,7 @@ internal class StakingAnalyticSender( } fun sendTransactionStakingAnalytics(value: StakingUiState) { - val confirmationState = value.confirmationState as? StakingStates.ConfirmationState.Data - val validatorState = confirmationState?.validatorState as? ValidatorState.Content + val validatorState = value.validatorState as? StakingStates.ValidatorState.Data val validatorName = validatorState?.chosenValidator?.name ?: return analyticsEventHandler.send( @@ -102,8 +102,7 @@ internal class StakingAnalyticSender( } fun sendTransactionStakingClickedAnalytics(value: StakingUiState) { - val confirmationState = value.confirmationState as? StakingStates.ConfirmationState.Data - val validatorState = confirmationState?.validatorState as? ValidatorState.Content + val validatorState = value.validatorState as? StakingStates.ValidatorState.Data val validatorName = validatorState?.chosenValidator?.name ?: return analyticsEventHandler.send( @@ -119,11 +118,9 @@ internal class StakingAnalyticSender( val confirmationState = value.confirmationState as? StakingStates.ConfirmationState.Data return when (value.actionType) { - StakingActionCommonType.ENTER -> StakingActionType.STAKE - StakingActionCommonType.EXIT -> StakingActionType.UNSTAKE - StakingActionCommonType.PENDING_REWARDS, - StakingActionCommonType.PENDING_OTHER, - -> confirmationState?.pendingAction?.type ?: StakingActionType.UNKNOWN + StakingActionCommonType.Enter -> StakingActionType.STAKE + StakingActionCommonType.Exit -> StakingActionType.UNSTAKE + is StakingActionCommonType.Pending -> confirmationState?.pendingAction?.type ?: StakingActionType.UNKNOWN } } } \ No newline at end of file diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/StakingStateController.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/StakingStateController.kt index 0b04c60e11..dd8919a82c 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/StakingStateController.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/StakingStateController.kt @@ -45,6 +45,12 @@ internal class StakingStateController @Inject constructor( mutableUiState.update(function = titleTransformer::transform) } + fun updateAll(vararg transformer: Transformer) { + transformer.forEach { mutableUiState.update(function = it::transform) } + mutableUiState.update(function = buttonsTransformer::transform) + mutableUiState.update(function = titleTransformer::transform) + } + fun clear() { mutableUiState.update { getInitialState() } mutableUiState.update(function = buttonsTransformer::transform) @@ -72,12 +78,13 @@ internal class StakingStateController @Inject constructor( currentStep = StakingStep.InitialInfo, initialInfoState = StakingStates.InitialInfoState.Empty(), amountState = AmountState.Empty(), + validatorState = StakingStates.ValidatorState.Empty(), rewardsValidatorsState = StakingStates.RewardsValidatorsState.Empty(), confirmationState = StakingStates.ConfirmationState.Empty(), isBalanceHidden = false, event = consumedEvent(), bottomSheetConfig = null, - actionType = StakingActionCommonType.ENTER, + actionType = StakingActionCommonType.Enter, buttonsState = NavigationButtonsState.Empty, ) } diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/StakingStateRouter.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/StakingStateRouter.kt index 69cca72194..e0e42070ac 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/StakingStateRouter.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/StakingStateRouter.kt @@ -23,12 +23,14 @@ internal class StakingStateRouter( fun onNextClick() { when (stateController.value.currentStep) { StakingStep.InitialInfo -> when (stateController.value.actionType) { - StakingActionCommonType.ENTER -> showAmount() - StakingActionCommonType.PENDING_OTHER, - StakingActionCommonType.EXIT, + StakingActionCommonType.Enter -> showAmount() + StakingActionCommonType.Pending.Other, + StakingActionCommonType.Exit, + StakingActionCommonType.Pending.Rewards, -> showConfirmation() - StakingActionCommonType.PENDING_REWARDS -> showRewardsValidators() + StakingActionCommonType.Pending.Restake -> showRestakeValidators() } + StakingStep.RestakeValidator, StakingStep.RewardsValidators, StakingStep.Validators, StakingStep.Amount, @@ -41,25 +43,31 @@ internal class StakingStateRouter( val uiState = stateController.uiState.value when (uiState.currentStep) { StakingStep.InitialInfo -> onBackClick() - StakingStep.Amount -> showInitial() + StakingStep.RestakeValidator, + StakingStep.RewardsValidators, + StakingStep.Amount, + -> showInitial() StakingStep.Confirmation -> { - if (uiState.actionType != StakingActionCommonType.ENTER) { + if (uiState.actionType != StakingActionCommonType.Enter) { showInitial() } else { showAmount() } } StakingStep.Validators -> showConfirmation() - StakingStep.RewardsValidators -> showInitial() } } + fun showValidators() { + stateController.update { it.copy(currentStep = StakingStep.Validators) } + } + private fun showInitial() { analyticSender.initialInfoScreen(stateController.value) stateController.update { it.copy(currentStep = StakingStep.InitialInfo) } } - private fun showRewardsValidators() { + fun showRewardsValidators() { analyticsEventsHandler.send( StakingAnalyticsEvents.RewardScreenOpened(stateController.value.cryptoCurrencySymbol), ) @@ -73,8 +81,8 @@ internal class StakingStateRouter( stateController.update { it.copy(currentStep = StakingStep.Amount) } } - fun showValidators() { - stateController.update { it.copy(currentStep = StakingStep.Validators) } + private fun showRestakeValidators() { + stateController.update { it.copy(currentStep = StakingStep.RestakeValidator) } } private fun showConfirmation() { diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/StakingUiState.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/StakingUiState.kt index 4bc8b84bda..56aa176637 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/StakingUiState.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/StakingUiState.kt @@ -11,6 +11,7 @@ import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.pullToRefresh.PullToRefreshConfig import com.tangem.domain.staking.model.PendingTransaction import com.tangem.domain.staking.model.stakekit.PendingAction +import com.tangem.domain.staking.model.stakekit.Yield import com.tangem.domain.staking.model.stakekit.action.StakingActionCommonType import com.tangem.features.staking.impl.presentation.state.bottomsheet.InfoType import com.tangem.features.staking.impl.presentation.state.events.StakingEvent @@ -34,6 +35,7 @@ internal data class StakingUiState( val amountState: AmountState, val rewardsValidatorsState: StakingStates.RewardsValidatorsState, val confirmationState: StakingStates.ConfirmationState, + val validatorState: StakingStates.ValidatorState, val isBalanceHidden: Boolean, val bottomSheetConfig: TangemBottomSheetConfig?, val actionType: StakingActionCommonType, @@ -45,10 +47,12 @@ internal data class StakingUiState( initialInfoState: StakingStates.InitialInfoState = this.initialInfoState, amountState: AmountState = this.amountState, confirmationState: StakingStates.ConfirmationState = this.confirmationState, + validatorState: StakingStates.ValidatorState = this.validatorState, ): StakingUiState = copy( initialInfoState = initialInfoState, amountState = amountState, confirmationState = confirmationState, + validatorState = validatorState, ) } @@ -85,13 +89,30 @@ internal sealed class StakingStates { ) : RewardsValidatorsState() } + sealed class ValidatorState : StakingStates() { + abstract val isClickable: Boolean + + data class Data( + override val isPrimaryButtonEnabled: Boolean, + override val isClickable: Boolean, + val isVisibleOnConfirmation: Boolean, + val chosenValidator: Yield.Validator, + val activeValidator: Yield.Validator?, + val availableValidators: List, + ) : ValidatorState() + + data class Empty( + override val isClickable: Boolean = false, + override val isPrimaryButtonEnabled: Boolean = false, + ) : ValidatorState() + } + /** Confirmation state */ sealed class ConfirmationState : StakingStates() { data class Data( override val isPrimaryButtonEnabled: Boolean, val innerState: InnerConfirmationStakingState, val feeState: FeeState, - val validatorState: ValidatorState, val pendingAction: PendingAction?, val pendingActions: ImmutableList?, val notifications: ImmutableList, @@ -112,6 +133,7 @@ enum class StakingStep { InitialInfo, RewardsValidators, Amount, + RestakeValidator, Confirmation, Validators, } \ No newline at end of file diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/ValidatorState.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/ValidatorState.kt deleted file mode 100644 index 7871149443..0000000000 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/ValidatorState.kt +++ /dev/null @@ -1,34 +0,0 @@ -package com.tangem.features.staking.impl.presentation.state - -import androidx.compose.runtime.Immutable -import com.tangem.domain.staking.model.stakekit.Yield - -@Immutable -internal sealed class ValidatorState { - - abstract val isClickable: Boolean - - data class Content( - override val isClickable: Boolean, - val chosenValidator: Yield.Validator, - val availableValidators: List, - ) : ValidatorState() - - data object Loading : ValidatorState() { - override val isClickable: Boolean - get() = false - } - - data object Error : ValidatorState() { - override val isClickable: Boolean - get() = false - } - - fun copySealed(isClickable: Boolean): ValidatorState { - return if (this is Content) { - copy(isClickable = isClickable) - } else { - this - } - } -} \ No newline at end of file diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/helpers/StakingFeeTransactionLoader.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/helpers/StakingFeeTransactionLoader.kt index 33559d6211..2daa181826 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/helpers/StakingFeeTransactionLoader.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/helpers/StakingFeeTransactionLoader.kt @@ -22,8 +22,7 @@ import com.tangem.domain.transaction.usecase.GetFeeUseCase import com.tangem.domain.wallets.models.UserWallet import com.tangem.features.staking.impl.presentation.state.StakingStateController import com.tangem.features.staking.impl.presentation.state.StakingStates -import com.tangem.features.staking.impl.presentation.state.ValidatorState -import com.tangem.features.staking.impl.presentation.state.utils.isSolanaWithdraw +import com.tangem.features.staking.impl.presentation.state.utils.isComposePendingActions import com.tangem.utils.extensions.orZero import dagger.assisted.Assisted import dagger.assisted.AssistedFactory @@ -48,8 +47,6 @@ internal class StakingFeeTransactionLoader @AssistedInject constructor( ) { suspend fun getFee( - pendingAction: PendingAction?, - pendingActions: ImmutableList?, onStakingFee: (Fee) -> Unit, onStakingFeeError: (StakingError) -> Unit, onApprovalFee: (TransactionFee) -> Unit, @@ -57,17 +54,20 @@ internal class StakingFeeTransactionLoader @AssistedInject constructor( ) { val state = stateController.value val confirmationState = state.confirmationState as? StakingStates.ConfirmationState.Data - ?: error("No confirmation state") - val validatorState = confirmationState.validatorState as? ValidatorState.Content + ?: error("Illegal state") + val validatorState = state.validatorState as? StakingStates.ValidatorState.Data ?: error("No validator provided") val amount = (state.amountState as? AmountState.Data)?.amountTextField?.cryptoAmount?.value ?: error("No amount provided") + val pendingAction = confirmationState.pendingAction + val pendingActions = confirmationState.pendingActions + val validatorAddress = validatorState.chosenValidator.address val approval = stakingApproval as? StakingApproval.Needed - if (approval != null && state.actionType == StakingActionCommonType.ENTER) { + if (approval != null && state.actionType == StakingActionCommonType.Enter) { val allowance = getAllowanceUseCase( userWalletId = userWallet.walletId, cryptoCurrency = cryptoCurrencyStatus.currency, @@ -114,7 +114,7 @@ internal class StakingFeeTransactionLoader @AssistedInject constructor( val sourceAddress = cryptoCurrencyStatus.value.networkAddress?.defaultAddress?.value ?: error("No available address") - val gasEstimate = if (isSolanaWithdraw(cryptoCurrencyStatus.currency.network.id.value, pendingActions)) { + val gasEstimate = if (isComposePendingActions(cryptoCurrencyStatus.currency.network.id.value, pendingActions)) { val result = coroutineScope { pendingActions?.map { action -> async { diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/helpers/StakingTransactionSender.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/helpers/StakingTransactionSender.kt index f334c448f4..90fc4e9295 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/helpers/StakingTransactionSender.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/helpers/StakingTransactionSender.kt @@ -21,9 +21,12 @@ import com.tangem.domain.txhistory.usecase.GetExplorerTransactionUrlUseCase import com.tangem.domain.utils.convertToSdkAmount import com.tangem.domain.wallets.models.UserWallet import com.tangem.features.staking.impl.analytics.StakingAnalyticsEvents -import com.tangem.features.staking.impl.presentation.state.* +import com.tangem.features.staking.impl.presentation.state.FeeState +import com.tangem.features.staking.impl.presentation.state.StakingStateController +import com.tangem.features.staking.impl.presentation.state.StakingStates +import com.tangem.features.staking.impl.presentation.state.StakingUiState import com.tangem.features.staking.impl.presentation.state.utils.checkAndCalculateSubtractedAmount -import com.tangem.features.staking.impl.presentation.state.utils.isSolanaWithdraw +import com.tangem.features.staking.impl.presentation.state.utils.isComposePendingActions import com.tangem.utils.extensions.orZero import dagger.assisted.Assisted import dagger.assisted.AssistedFactory @@ -99,7 +102,7 @@ internal class StakingTransactionSender @AssistedInject constructor( } private fun composePendingTransaction(confirmationState: StakingStates.ConfirmationState.Data): PendingTransaction { - return confirmationState.possiblePendingTransaction ?: composeStakeTransaction(confirmationState) + return confirmationState.possiblePendingTransaction ?: composeStakeTransaction() } private suspend fun getStakingTransactions( @@ -107,11 +110,11 @@ internal class StakingTransactionSender @AssistedInject constructor( confirmationState: StakingStates.ConfirmationState.Data, onConstructError: (StakingError) -> Unit, ) = coroutineScope { - val isAllWithdrawAction = isSolanaWithdraw( + val isComposePendingActions = isComposePendingActions( cryptoCurrencyStatus.currency.network.id.value, confirmationState.pendingActions, ) - if (isAllWithdrawAction) { + if (isComposePendingActions) { confirmationState.pendingActions?.map { action -> async { getStakingTransaction( @@ -172,7 +175,7 @@ internal class StakingTransactionSender @AssistedInject constructor( action: PendingAction? = confirmationState.pendingAction, onConstructError: (StakingError) -> Unit, ): List { - val validatorState = confirmationState.validatorState as? ValidatorState.Content + val validatorState = state.validatorState as? StakingStates.ValidatorState.Data ?: error("No validator provided") val fee = (confirmationState.feeState as? FeeState.Content)?.fee ?: error("No fee provided") @@ -278,7 +281,7 @@ internal class StakingTransactionSender @AssistedInject constructor( private fun getAmount(amountState: AmountState.Data, fee: Fee, reduceAmountBy: BigDecimal?): BigDecimal { val amountValue = amountState.amountTextField.cryptoAmount.value ?: error("No amount value") val feeValue = fee.amount.value ?: error("No fee value") - val isEnterAction = stateController.value.actionType == StakingActionCommonType.ENTER + val isEnterAction = stateController.value.actionType == StakingActionCommonType.Enter return checkAndCalculateSubtractedAmount( isAmountSubtractAvailable = isAmountSubtractAvailable && isEnterAction, @@ -289,7 +292,7 @@ internal class StakingTransactionSender @AssistedInject constructor( ) } - private fun composeStakeTransaction(confirmationState: StakingStates.ConfirmationState.Data): PendingTransaction { + private fun composeStakeTransaction(): PendingTransaction { val state = stateController.value val yieldBalance = cryptoCurrencyStatus.value.yieldBalance as? YieldBalance.Data @@ -301,7 +304,7 @@ internal class StakingTransactionSender @AssistedInject constructor( type = BalanceType.STAKED, amount = (state.amountState as? AmountState.Data)?.amountTextField?.cryptoAmount?.value ?: BigDecimal.ZERO, rawCurrencyId = cryptoCurrencyStatus.currency.id.rawCurrencyId, - validator = (confirmationState.validatorState as? ValidatorState.Content)?.chosenValidator, + validator = (state.validatorState as? StakingStates.ValidatorState.Data)?.chosenValidator, balancesId = yieldBalance?.getBalancesUniqueId() ?: 0, ) } diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/previewdata/ConfirmationStatePreviewData.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/previewdata/ConfirmationStatePreviewData.kt index d9c7263d8e..016a6a0625 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/previewdata/ConfirmationStatePreviewData.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/previewdata/ConfirmationStatePreviewData.kt @@ -7,8 +7,6 @@ import com.tangem.core.ui.extensions.resourceReference import com.tangem.core.ui.extensions.stringReference import com.tangem.core.ui.extensions.wrappedList import com.tangem.domain.appcurrency.model.AppCurrency -import com.tangem.domain.staking.model.stakekit.Yield -import com.tangem.domain.staking.model.stakekit.Yield.Validator.ValidatorStatus import com.tangem.features.staking.impl.R import com.tangem.features.staking.impl.presentation.state.* import kotlinx.collections.immutable.persistentListOf @@ -16,48 +14,6 @@ import java.math.BigDecimal internal object ConfirmationStatePreviewData { - private val validatorList = listOf( - Yield.Validator( - address = "0xa6e768fef2d1af36c0cfdb276422e7881a83e951", - status = ValidatorStatus.ACTIVE, - name = "Luganodes", - image = "https://assets.stakek.it/validators/luganodes.png", - apr = BigDecimal("0.054823398040640445"), - commission = 0.1, - stakedBalance = "355544384.45009977", - website = "https://luganodes.com/", - votingPower = 0.09778360195377911, - preferred = true, - isStrategicPartner = false, - ), - Yield.Validator( - address = "0x35b1ca0f398905cf752e6fe122b51c88022fca32", - status = ValidatorStatus.ACTIVE, - name = "InfStones", - image = "https://assets.stakek.it/validators/infstones.png", - apr = BigDecimal("0.057786472172836965"), - commission = 0.05, - stakedBalance = "12495684.05643019", - website = "https://infstones.com/", - votingPower = 0.0034366257754399774, - preferred = true, - isStrategicPartner = false, - ), - Yield.Validator( - address = "0xd14a87025109013b0a2354a775cb335f926af65a", - status = ValidatorStatus.ACTIVE, - name = "Kiln", - image = "https://assets.stakek.it/validators/kiln.png", - apr = BigDecimal("0.057786472172836965"), - commission = 0.05, - stakedBalance = "85400369.96393165", - website = "https://infstones.com/", - votingPower = 0.023487238579718264, - preferred = true, - isStrategicPartner = true, - ), - ) - private val fee = Fee.Common( amount = Amount( currencySymbol = "MATIC", @@ -77,11 +33,6 @@ internal object ConfirmationStatePreviewData { isFeeApproximate = false, isFeeConvertibleToFiat = true, ), - validatorState = ValidatorState.Content( - isClickable = true, - chosenValidator = validatorList[0], - availableValidators = validatorList, - ), footerText = stringReference("You stake \$715.11 and will be receiving ~\$35 monthly"), notifications = persistentListOf( StakingNotification.Info.EarnRewards( diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/previewdata/ValidatorStatePreviewData.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/previewdata/ValidatorStatePreviewData.kt new file mode 100644 index 0000000000..225057c19c --- /dev/null +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/previewdata/ValidatorStatePreviewData.kt @@ -0,0 +1,60 @@ +package com.tangem.features.staking.impl.presentation.state.previewdata + +import com.tangem.domain.staking.model.stakekit.Yield +import com.tangem.domain.staking.model.stakekit.Yield.Validator.ValidatorStatus +import com.tangem.features.staking.impl.presentation.state.StakingStates +import java.math.BigDecimal + +internal object ValidatorStatePreviewData { + + private val validatorList = listOf( + Yield.Validator( + address = "0xa6e768fef2d1af36c0cfdb276422e7881a83e951", + status = ValidatorStatus.ACTIVE, + name = "Luganodes", + image = "https://assets.stakek.it/validators/luganodes.png", + apr = BigDecimal("0.054823398040640445"), + commission = 0.1, + stakedBalance = "355544384.45009977", + website = "https://luganodes.com/", + votingPower = 0.09778360195377911, + preferred = true, + isStrategicPartner = false, + ), + Yield.Validator( + address = "0x35b1ca0f398905cf752e6fe122b51c88022fca32", + status = ValidatorStatus.ACTIVE, + name = "InfStones", + image = "https://assets.stakek.it/validators/infstones.png", + apr = BigDecimal("0.057786472172836965"), + commission = 0.05, + stakedBalance = "12495684.05643019", + website = "https://infstones.com/", + votingPower = 0.0034366257754399774, + preferred = true, + isStrategicPartner = true, + ), + Yield.Validator( + address = "0xd14a87025109013b0a2354a775cb335f926af65a", + status = ValidatorStatus.ACTIVE, + name = "Kiln", + image = "https://assets.stakek.it/validators/kiln.png", + apr = BigDecimal("0.057786472172836965"), + commission = 0.05, + stakedBalance = "85400369.96393165", + website = "https://infstones.com/", + votingPower = 0.023487238579718264, + preferred = true, + isStrategicPartner = false, + ), + ) + + val validatorState = StakingStates.ValidatorState.Data( + availableValidators = validatorList, + chosenValidator = validatorList.first(), + isPrimaryButtonEnabled = true, + activeValidator = null, + isClickable = true, + isVisibleOnConfirmation = true, + ) +} \ No newline at end of file diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/stub/StakingClickIntentsStub.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/stub/StakingClickIntentsStub.kt index 716cab0e93..9da7141f5a 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/stub/StakingClickIntentsStub.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/stub/StakingClickIntentsStub.kt @@ -2,14 +2,11 @@ package com.tangem.features.staking.impl.presentation.state.stub import com.tangem.common.ui.bottomsheet.permission.state.ApproveType import com.tangem.common.ui.notifications.NotificationUM -import com.tangem.domain.staking.model.stakekit.PendingAction import com.tangem.domain.staking.model.stakekit.Yield -import com.tangem.domain.staking.model.stakekit.action.StakingActionCommonType import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.features.staking.impl.presentation.state.BalanceState import com.tangem.features.staking.impl.presentation.state.bottomsheet.InfoType import com.tangem.features.staking.impl.presentation.viewmodel.StakingClickIntents -import kotlinx.collections.immutable.ImmutableList import java.math.BigDecimal @Suppress("TooManyFunctions") @@ -17,13 +14,7 @@ internal object StakingClickIntentsStub : StakingClickIntents { override fun onBackClick() {} - override fun onNextClick( - actionTypeToOverwrite: StakingActionCommonType?, - pendingAction: PendingAction?, - pendingActions: ImmutableList?, - balanceState: BalanceState?, - ) { - } + override fun onNextClick(balanceState: BalanceState?) {} override fun onActionClick() {} @@ -67,8 +58,7 @@ internal object StakingClickIntentsStub : StakingClickIntents { override fun onActiveStake(activeStake: BalanceState) {} - override fun getFee(pendingAction: PendingAction?, pendingActions: ImmutableList?) { - } + override fun getFee() {} override fun onAmountReduceByClick( reduceAmountBy: BigDecimal, diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/ActionTypeActiveStakeTransformer.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/ActionTypeActiveStakeTransformer.kt deleted file mode 100644 index 31e1f97f96..0000000000 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/ActionTypeActiveStakeTransformer.kt +++ /dev/null @@ -1,33 +0,0 @@ -package com.tangem.features.staking.impl.presentation.state.transformers - -import com.tangem.domain.staking.model.stakekit.BalanceType -import com.tangem.domain.staking.model.stakekit.action.StakingActionCommonType -import com.tangem.domain.tokens.model.CryptoCurrencyStatus -import com.tangem.domain.tokens.model.Network -import com.tangem.features.staking.impl.presentation.state.* -import com.tangem.lib.crypto.BlockchainUtils.isTron -import com.tangem.utils.transformer.Transformer - -internal class ActionTypeActiveStakeTransformer( - private val cryptoCurrencyStatus: CryptoCurrencyStatus, - private val activeStake: BalanceState, -) : Transformer { - - override fun transform(prevState: StakingUiState): StakingUiState { - val isTronStakedBalance = isTronStakedBalance( - networkId = cryptoCurrencyStatus.currency.network.id, - activeStake = activeStake, - ) - val actionType = if (activeStake.pendingActions.isEmpty() || isTronStakedBalance) { - StakingActionCommonType.EXIT - } else { - StakingActionCommonType.PENDING_OTHER - } - - return prevState.copy(actionType = actionType) - } - - private fun isTronStakedBalance(networkId: Network.ID, activeStake: BalanceState): Boolean { - return isTron(networkId.value) && activeStake.type == BalanceType.STAKED - } -} \ No newline at end of file diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/AddStakingNotificationsTransformer.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/AddStakingNotificationsTransformer.kt index c8066c1dc2..5c3bfbd6e0 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/AddStakingNotificationsTransformer.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/AddStakingNotificationsTransformer.kt @@ -65,7 +65,7 @@ internal class AddStakingNotificationsTransformer( val feeValue = feeState?.fee?.amount?.value.orZero() val reduceAmountBy = confirmationState.reduceAmountBy.orZero() - val isEnterAction = prevState.actionType == StakingActionCommonType.ENTER + val isEnterAction = prevState.actionType == StakingActionCommonType.Enter val isFeeCoverage = checkFeeCoverage( amountValue = amountValue, feeValue = feeValue, @@ -93,12 +93,7 @@ internal class AddStakingNotificationsTransformer( prevState = prevState, feeError = feeError, sendingAmount = sendingAmount, - onReload = { - prevState.clickIntents.getFee( - confirmationState.pendingAction, - confirmationState.pendingActions, - ) - }, + onReload = prevState.clickIntents::getFee, feeValue = feeValue, ) // warnings @@ -228,7 +223,7 @@ internal class AddStakingNotificationsTransformer( val showNotification = sendingAmount + feeAmount > balance if (showNotification) { - val notification = if (actionType == StakingActionCommonType.ENTER) { + val notification = if (actionType == StakingActionCommonType.Enter) { NotificationUM.Error.TotalExceedsBalance } else { with(cryptoCurrencyStatus.currency) { @@ -249,9 +244,9 @@ internal class AddStakingNotificationsTransformer( private fun MutableList.addInfoNotifications(prevState: StakingUiState) { when (prevState.actionType) { - StakingActionCommonType.EXIT -> addExitInfoNotifications() - StakingActionCommonType.ENTER -> addEnterInfoNotifications() - else -> addPendingInfoNotifications(prevState) + StakingActionCommonType.Enter -> addEnterInfoNotifications() + StakingActionCommonType.Exit -> addExitInfoNotifications() + is StakingActionCommonType.Pending -> addPendingInfoNotifications(prevState) } } @@ -312,6 +307,10 @@ internal class AddStakingNotificationsTransformer( resourceReference(R.string.staking_revote) to resourceReference(R.string.staking_notifications_revote_tron_text) } + StakingActionType.RESTAKE -> { + resourceReference(R.string.staking_restake) to + resourceReference(R.string.staking_notification_restake_text) + } else -> null to null } diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetActionToExecuteTransformer.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetActionToExecuteTransformer.kt deleted file mode 100644 index e3adab9f50..0000000000 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetActionToExecuteTransformer.kt +++ /dev/null @@ -1,25 +0,0 @@ -package com.tangem.features.staking.impl.presentation.state.transformers - -import com.tangem.domain.staking.model.stakekit.PendingAction -import com.tangem.domain.staking.model.stakekit.action.StakingActionCommonType -import com.tangem.features.staking.impl.presentation.state.StakingStates -import com.tangem.features.staking.impl.presentation.state.StakingUiState -import com.tangem.utils.transformer.Transformer -import kotlinx.collections.immutable.ImmutableList - -internal class SetActionToExecuteTransformer( - private val actionTypeToOverwrite: StakingActionCommonType, - private val pendingAction: PendingAction?, - private val pendingActions: ImmutableList?, -) : Transformer { - override fun transform(prevState: StakingUiState): StakingUiState { - val confirmationState = prevState.confirmationState as? StakingStates.ConfirmationState.Data - return prevState.copy( - actionType = actionTypeToOverwrite, - confirmationState = confirmationState?.copy( - pendingAction = pendingAction, - pendingActions = pendingActions, - ) ?: StakingStates.ConfirmationState.Empty(), - ) - } -} \ No newline at end of file diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetButtonsStateTransformer.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetButtonsStateTransformer.kt index 52251d7408..c2761a0713 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetButtonsStateTransformer.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetButtonsStateTransformer.kt @@ -109,6 +109,7 @@ internal class SetButtonsStateTransformer( StakingStep.Confirmation -> getConfirmationButtonText() StakingStep.Validators -> resourceReference(R.string.common_continue) StakingStep.Amount, + StakingStep.RestakeValidator, StakingStep.RewardsValidators, -> resourceReference(R.string.common_next) } @@ -120,17 +121,15 @@ internal class SetButtonsStateTransformer( resourceReference(R.string.common_close) } else { when (actionType) { - StakingActionCommonType.ENTER -> { + StakingActionCommonType.Enter -> { if (confirmationState.isApprovalNeeded) { resourceReference(R.string.give_permission_title) } else { resourceReference(R.string.common_stake) } } - StakingActionCommonType.EXIT -> resourceReference(R.string.common_unstake) - StakingActionCommonType.PENDING_OTHER, - StakingActionCommonType.PENDING_REWARDS, - -> confirmationState.pendingAction?.type.getPendingActionTitle() + StakingActionCommonType.Exit -> resourceReference(R.string.common_unstake) + is StakingActionCommonType.Pending -> confirmationState.pendingAction?.type.getPendingActionTitle() } } } else { @@ -140,18 +139,11 @@ internal class SetButtonsStateTransformer( private fun StakingUiState.onPrimaryClick() { when (currentStep) { - StakingStep.InitialInfo -> clickIntents.onEnterClick() - StakingStep.Validators -> { - if (confirmationState is StakingStates.ConfirmationState.Data) { - clickIntents.onNextClick( - pendingActions = confirmationState.pendingActions, - pendingAction = confirmationState.pendingAction, - ) - } else { - clickIntents.onNextClick() - } - } - StakingStep.Amount -> clickIntents.onNextClick() + StakingStep.InitialInfo -> clickIntents.onNextClick() + StakingStep.Validators, + StakingStep.RestakeValidator, + -> clickIntents.onNextClick() + StakingStep.Amount -> clickIntents.onEnterClick() StakingStep.Confirmation -> onConfirmationClick() StakingStep.RewardsValidators -> Unit } @@ -162,7 +154,7 @@ internal class SetButtonsStateTransformer( if (confirmationState.innerState == InnerConfirmationStakingState.COMPLETED) { clickIntents.onNextClick() } else { - val isEnterAction = actionType == StakingActionCommonType.ENTER + val isEnterAction = actionType == StakingActionCommonType.Enter val isApproveNeeded = confirmationState.isApprovalNeeded if (isEnterAction && isApproveNeeded) { @@ -179,6 +171,7 @@ internal class SetButtonsStateTransformer( private fun StakingStep.isPrevButtonVisible(): Boolean = when (this) { StakingStep.InitialInfo, StakingStep.RewardsValidators, + StakingStep.RestakeValidator, StakingStep.Confirmation, StakingStep.Validators, -> false @@ -192,7 +185,9 @@ internal class SetButtonsStateTransformer( StakingStep.Amount -> amountState.isPrimaryButtonEnabled StakingStep.Confirmation -> confirmationState.isPrimaryButtonEnabled StakingStep.RewardsValidators -> rewardsValidatorsState.isPrimaryButtonEnabled - StakingStep.Validators -> true + StakingStep.RestakeValidator, + StakingStep.Validators, + -> true } } } \ No newline at end of file diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetConfirmationStateAssentTransformer.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetConfirmationStateAssentTransformer.kt index 196784785f..85443b4096 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetConfirmationStateAssentTransformer.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetConfirmationStateAssentTransformer.kt @@ -2,22 +2,17 @@ package com.tangem.features.staking.impl.presentation.state.transformers import com.tangem.blockchain.common.transaction.Fee import com.tangem.domain.appcurrency.model.AppCurrency -import com.tangem.domain.staking.model.stakekit.PendingAction import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.features.staking.impl.presentation.state.FeeState -import com.tangem.features.staking.impl.presentation.state.InnerConfirmationStakingState import com.tangem.features.staking.impl.presentation.state.StakingStates import com.tangem.features.staking.impl.presentation.state.StakingUiState import com.tangem.utils.Provider import com.tangem.utils.transformer.Transformer -import kotlinx.collections.immutable.ImmutableList internal class SetConfirmationStateAssentTransformer( private val appCurrencyProvider: Provider, private val feeCryptoCurrencyStatus: CryptoCurrencyStatus?, private val fee: Fee, - private val action: PendingAction?, - private val actions: ImmutableList?, ) : Transformer { override fun transform(prevState: StakingUiState): StakingUiState { @@ -30,7 +25,6 @@ internal class SetConfirmationStateAssentTransformer( if (this is StakingStates.ConfirmationState.Data) { val isFeeConvertibleToFiat = feeCryptoCurrencyStatus?.currency?.network?.hasFiatFeeRate == true return copy( - innerState = InnerConfirmationStakingState.ASSENT, feeState = FeeState.Content( fee = fee, rate = feeCryptoCurrencyStatus?.value?.fiatRate, @@ -38,11 +32,7 @@ internal class SetConfirmationStateAssentTransformer( appCurrency = appCurrencyProvider(), isFeeApproximate = false, ), - validatorState = validatorState.copySealed(isClickable = true), - pendingAction = action, - pendingActions = actions, isPrimaryButtonEnabled = true, - isApprovalNeeded = false, ) } else { return this diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetConfirmationStateCompletedTransformer.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetConfirmationStateCompletedTransformer.kt index db27e0c2d9..4ecbf95344 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetConfirmationStateCompletedTransformer.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetConfirmationStateCompletedTransformer.kt @@ -16,11 +16,10 @@ internal class SetConfirmationStateCompletedTransformer( } private fun StakingStates.ConfirmationState.copyWrapped(): StakingStates.ConfirmationState { - if (this is StakingStates.ConfirmationState.Data) { - return copy( + return if (this is StakingStates.ConfirmationState.Data) { + copy( isPrimaryButtonEnabled = true, innerState = InnerConfirmationStakingState.COMPLETED, - validatorState = validatorState.copySealed(isClickable = false), footerText = TextReference.EMPTY, notifications = persistentListOf(), transactionDoneState = TransactionDoneState.Content( @@ -29,7 +28,7 @@ internal class SetConfirmationStateCompletedTransformer( ), ) } else { - return this + this } } } \ No newline at end of file diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetConfirmationStateEmptyTransformer.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetConfirmationStateEmptyTransformer.kt new file mode 100644 index 0000000000..84cd59a951 --- /dev/null +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetConfirmationStateEmptyTransformer.kt @@ -0,0 +1,16 @@ +package com.tangem.features.staking.impl.presentation.state.transformers + +import com.tangem.domain.staking.model.stakekit.action.StakingActionCommonType +import com.tangem.features.staking.impl.presentation.state.StakingStates +import com.tangem.features.staking.impl.presentation.state.StakingUiState +import com.tangem.utils.transformer.Transformer + +internal object SetConfirmationStateEmptyTransformer : Transformer { + override fun transform(prevState: StakingUiState): StakingUiState { + return prevState.copy( + actionType = StakingActionCommonType.Enter, + validatorState = StakingStates.ValidatorState.Empty(), + confirmationState = StakingStates.ConfirmationState.Empty(), + ) + } +} \ No newline at end of file diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetConfirmationStateInProgressTransformer.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetConfirmationStateInProgressTransformer.kt index 24bdd43c84..9043cc8ae9 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetConfirmationStateInProgressTransformer.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetConfirmationStateInProgressTransformer.kt @@ -19,7 +19,6 @@ internal class SetConfirmationStateInProgressTransformer : Transformer? = null, + private val pendingAction: PendingAction? = pendingActions?.firstOrNull(), +) : Transformer { + + private val networkId + get() = cryptoCurrencyStatus.currency.network.id.value + + private val isComposePendingActions + get() = isComposePendingActions(networkId, pendingActions) + + private val isTronStakedBalance + get() = isTronStakedBalance(networkId, pendingAction) + + private val isExit: Boolean + get() = pendingAction == null && pendingActions?.isEmpty() == true || isTronStakedBalance + + override fun transform(prevState: StakingUiState): StakingUiState { + val actionType = when { + isEnter -> StakingActionCommonType.Enter + isExit -> StakingActionCommonType.Exit + else -> when (pendingAction?.type) { + StakingActionType.STAKE -> StakingActionCommonType.Enter + StakingActionType.UNSTAKE -> StakingActionCommonType.Exit + StakingActionType.CLAIM_REWARDS, + StakingActionType.RESTAKE_REWARDS, + -> StakingActionCommonType.Pending.Rewards + StakingActionType.VOTE_LOCKED, + StakingActionType.RESTAKE, + -> StakingActionCommonType.Pending.Restake + else -> StakingActionCommonType.Pending.Other + } + } + + return prevState.copy( + actionType = actionType, + confirmationState = StakingStates.ConfirmationState.Data( + isPrimaryButtonEnabled = false, + innerState = InnerConfirmationStakingState.ASSENT, + feeState = FeeState.Loading, + notifications = persistentListOf(), + footerText = TextReference.EMPTY, + transactionDoneState = TransactionDoneState.Empty, + isApprovalNeeded = stakingApproval is StakingApproval.Needed, + reduceAmountBy = null, + pendingAction = pendingAction, + pendingActions = pendingActions.takeIf { isComposePendingActions }, + possiblePendingTransaction = null, + ), + ) + } +} \ No newline at end of file diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetConfirmationStateLoadingTransformer.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetConfirmationStateLoadingTransformer.kt index e32fc821f9..dae491e1b8 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetConfirmationStateLoadingTransformer.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetConfirmationStateLoadingTransformer.kt @@ -10,10 +10,11 @@ import com.tangem.domain.staking.model.stakekit.Yield import com.tangem.domain.staking.model.stakekit.action.StakingActionCommonType import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.features.staking.impl.R -import com.tangem.features.staking.impl.presentation.state.* +import com.tangem.features.staking.impl.presentation.state.FeeState +import com.tangem.features.staking.impl.presentation.state.StakingStates +import com.tangem.features.staking.impl.presentation.state.StakingUiState import com.tangem.features.staking.impl.presentation.state.utils.getRewardSchedule import com.tangem.utils.transformer.Transformer -import kotlinx.collections.immutable.persistentListOf internal class SetConfirmationStateLoadingTransformer( private val yield: Yield, @@ -22,39 +23,21 @@ internal class SetConfirmationStateLoadingTransformer( ) : Transformer { override fun transform(prevState: StakingUiState): StakingUiState { - val filteredValidators = yield.validators.filter { - it.preferred - } val possibleConfirmationState = prevState.confirmationState as? StakingStates.ConfirmationState.Data - val possibleValidatorState = possibleConfirmationState?.validatorState as? ValidatorState.Content - val chosenValidator = possibleValidatorState?.chosenValidator ?: filteredValidators[0] return prevState.copy( - confirmationState = StakingStates.ConfirmationState.Data( + confirmationState = possibleConfirmationState?.copy( isPrimaryButtonEnabled = false, - innerState = InnerConfirmationStakingState.ASSENT, feeState = FeeState.Loading, - validatorState = ValidatorState.Content( - isClickable = true, - chosenValidator = chosenValidator, - availableValidators = filteredValidators, - ), - notifications = persistentListOf(), footerText = getFooter(prevState), - transactionDoneState = TransactionDoneState.Empty, - pendingAction = possibleConfirmationState?.pendingAction, - pendingActions = possibleConfirmationState?.pendingActions, - isApprovalNeeded = false, - reduceAmountBy = null, - possiblePendingTransaction = null, - ), + ) ?: prevState.confirmationState, ) } private fun getFooter(state: StakingUiState): TextReference { val amountState = state.amountState as? AmountState.Data - val isEnterAction = state.actionType == StakingActionCommonType.ENTER + val isEnterAction = state.actionType == StakingActionCommonType.Enter val amountDecimal = amountState?.amountTextField?.fiatAmount?.value val amountValue = BigDecimalFormatter.formatFiatAmount( diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetConfirmationStateResetAssentTransformer.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetConfirmationStateResetAssentTransformer.kt index ee866db72c..1c1c7103dc 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetConfirmationStateResetAssentTransformer.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetConfirmationStateResetAssentTransformer.kt @@ -13,7 +13,6 @@ internal object SetConfirmationStateResetAssentTransformer : Transformer, private val userWalletProvider: Provider, @@ -76,7 +74,7 @@ internal class SetInitialDataStateTransformer( currentStep = StakingStep.InitialInfo, initialInfoState = createInitialInfoState(), amountState = createInitialAmountState(), - confirmationState = createInitialConfirmationState(), + confirmationState = StakingStates.ConfirmationState.Empty(), rewardsValidatorsState = rewardsValidatorStateConverter.convert(Unit), bottomSheetConfig = null, ) @@ -87,7 +85,7 @@ internal class SetInitialDataStateTransformer( return StakingStates.InitialInfoState.Data( isPrimaryButtonEnabled = !cryptoCurrencyStatusProvider().value.amount.isNullOrZero(), showBanner = !isAnyTokenStaked && yieldBalance == InnerYieldBalanceState.Empty, - aprRange = getAprRange(yield.validators), + aprRange = getAprRange(yield.preferredValidators), infoItems = getInfoItems(), onInfoClick = clickIntents::onInfoClick, yieldBalance = yieldBalance, @@ -113,7 +111,7 @@ internal class SetInitialDataStateTransformer( } private fun createAnnualPercentageRateItem(): RoundedListWithDividersItemData { - val validators = yield.validators + val validators = yield.preferredValidators return RoundedListWithDividersItemData( id = R.string.staking_details_annual_percentage_rate, startText = TextReference.Res(R.string.staking_details_annual_percentage_rate), @@ -214,23 +212,6 @@ internal class SetInitialDataStateTransformer( return amountStateConverter.convert("") } - private fun createInitialConfirmationState(): StakingStates.ConfirmationState { - return StakingStates.ConfirmationState.Data( - isPrimaryButtonEnabled = false, - innerState = InnerConfirmationStakingState.ASSENT, - feeState = FeeState.Loading, - validatorState = ValidatorState.Loading, - notifications = persistentListOf(), - footerText = TextReference.EMPTY, - transactionDoneState = TransactionDoneState.Empty, - pendingAction = null, - pendingActions = null, - isApprovalNeeded = isApprovalNeeded, - reduceAmountBy = null, - possiblePendingTransaction = null, - ) - } - private fun getAprRange(validators: List): TextReference { val aprValues = validators.mapNotNull { it.apr } diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetTitleTransformer.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetTitleTransformer.kt index 578af70403..a29c1a8a85 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetTitleTransformer.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetTitleTransformer.kt @@ -20,7 +20,9 @@ internal object SetTitleTransformer : Transformer { val title = when (currentStep) { StakingStep.Amount -> resourceReference(R.string.send_amount_label) - StakingStep.Validators -> resourceReference(R.string.staking_validators) + StakingStep.RestakeValidator, + StakingStep.Validators, + -> resourceReference(R.string.staking_validators) StakingStep.RewardsValidators -> resourceReference(R.string.common_claim_rewards) StakingStep.InitialInfo -> resourceReference( R.string.staking_title_stake, @@ -29,11 +31,11 @@ internal object SetTitleTransformer : Transformer { StakingStep.Confirmation -> { when (actionType) { - StakingActionCommonType.ENTER -> resourceReference( + StakingActionCommonType.Enter -> resourceReference( R.string.staking_title_stake, wrappedList(prevState.cryptoCurrencyName), ) - StakingActionCommonType.EXIT -> resourceReference( + StakingActionCommonType.Exit -> resourceReference( R.string.staking_title_unstake, wrappedList(prevState.cryptoCurrencyName), ) @@ -51,7 +53,7 @@ internal object SetTitleTransformer : Transformer { } } - val subtitle = if (currentStep == StakingStep.Confirmation && actionType == StakingActionCommonType.ENTER) { + val subtitle = if (currentStep == StakingStep.Confirmation && actionType == StakingActionCommonType.Enter) { stringReference(prevState.walletName) } else { null diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/amount/AmountRequirementStateTransformer.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/amount/AmountRequirementStateTransformer.kt index d669d5a7e9..284f9b239b 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/amount/AmountRequirementStateTransformer.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/amount/AmountRequirementStateTransformer.kt @@ -102,7 +102,7 @@ internal class AmountRequirementStateTransformer( private fun isIntegerOnlyError(amountState: AmountState.Data, actionType: StakingActionCommonType): Boolean { val cryptoAmountValue = amountState.amountTextField.cryptoAmount.value ?: return false - val isEnter = actionType == StakingActionCommonType.ENTER + val isEnter = actionType == StakingActionCommonType.Enter val isTron = isTron(cryptoCurrencyStatus.currency.network.id.value) val isIntegerOnly = cryptoAmountValue.isZero() || cryptoAmountValue.remainder(BigDecimal.ONE).isZero() diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/approval/SetConfirmationStateAssentApprovalTransformer.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/approval/SetConfirmationStateAssentApprovalTransformer.kt index 939c6877dc..6f331795e4 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/approval/SetConfirmationStateAssentApprovalTransformer.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/approval/SetConfirmationStateAssentApprovalTransformer.kt @@ -35,7 +35,6 @@ internal class SetConfirmationStateAssentApprovalTransformer( appCurrency = appCurrencyProvider(), isFeeApproximate = false, ), - validatorState = validatorState.copySealed(isClickable = true), isPrimaryButtonEnabled = true, isApprovalNeeded = true, ) diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/approval/ShowApprovalBottomSheetTransformer.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/approval/ShowApprovalBottomSheetTransformer.kt index b5631913ad..ca32d6c1be 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/approval/ShowApprovalBottomSheetTransformer.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/approval/ShowApprovalBottomSheetTransformer.kt @@ -12,7 +12,6 @@ import com.tangem.features.staking.impl.R import com.tangem.features.staking.impl.presentation.state.FeeState import com.tangem.features.staking.impl.presentation.state.StakingStates import com.tangem.features.staking.impl.presentation.state.StakingUiState -import com.tangem.features.staking.impl.presentation.state.ValidatorState import com.tangem.utils.Provider import com.tangem.utils.transformer.Transformer @@ -28,7 +27,7 @@ internal class ShowApprovalBottomSheetTransformer( val amountState = prevState.amountState as? AmountState.Data ?: return prevState val confirmationState = prevState.confirmationState as? StakingStates.ConfirmationState.Data ?: return prevState - val validatorState = confirmationState.validatorState as? ValidatorState.Content ?: return prevState + val validatorState = prevState.validatorState as? StakingStates.ValidatorState.Data ?: return prevState val feeState = confirmationState.feeState as? FeeState.Content ?: return prevState val fee = feeState.fee ?: return prevState diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/validator/ValidatorSelectChangeTransformer.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/validator/ValidatorSelectChangeTransformer.kt index e6ba4e32db..0f79a3ab26 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/validator/ValidatorSelectChangeTransformer.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/validator/ValidatorSelectChangeTransformer.kt @@ -1,29 +1,45 @@ package com.tangem.features.staking.impl.presentation.state.transformers.validator import com.tangem.domain.staking.model.stakekit.Yield +import com.tangem.domain.staking.model.stakekit.action.StakingActionCommonType +import com.tangem.domain.staking.model.stakekit.action.StakingActionType import com.tangem.features.staking.impl.presentation.state.StakingStates +import com.tangem.features.staking.impl.presentation.state.StakingStep import com.tangem.features.staking.impl.presentation.state.StakingUiState -import com.tangem.features.staking.impl.presentation.state.ValidatorState import com.tangem.utils.transformer.Transformer internal class ValidatorSelectChangeTransformer( + private val yield: Yield, private val selectedValidator: Yield.Validator?, ) : Transformer { override fun transform(prevState: StakingUiState): StakingUiState { - val confirmationState = prevState.confirmationState as? StakingStates.ConfirmationState.Data ?: return prevState - selectedValidator ?: return prevState - val validatorState = (confirmationState.validatorState as? ValidatorState.Content)?.copy( - chosenValidator = selectedValidator, - ) ?: ValidatorState.Content( - isClickable = false, - availableValidators = emptyList(), - chosenValidator = selectedValidator, - ) + val validatorState = prevState.validatorState as? StakingStates.ValidatorState.Data + val confirmationState = prevState.confirmationState as? StakingStates.ConfirmationState.Data + + val isRestake = prevState.actionType == StakingActionCommonType.Pending.Restake + val isEnter = prevState.actionType == StakingActionCommonType.Enter + val isFromInfoScreen = prevState.currentStep == StakingStep.InitialInfo + val isVoteLocked = confirmationState?.pendingAction?.type == StakingActionType.VOTE_LOCKED + + val activeValidator = selectedValidator.takeIf { isFromInfoScreen && isRestake } + ?: validatorState?.activeValidator + val filteredValidators = yield.preferredValidators.filterNot { it == activeValidator } + + val selectedValidator = if (isRestake && isFromInfoScreen) { + filteredValidators.firstOrNull() + } else { + selectedValidator + } return prevState.copy( - confirmationState = confirmationState.copy( - validatorState = validatorState, + validatorState = StakingStates.ValidatorState.Data( + chosenValidator = selectedValidator ?: yield.preferredValidators.first(), + availableValidators = filteredValidators, + isPrimaryButtonEnabled = true, + isClickable = true, + activeValidator = activeValidator, + isVisibleOnConfirmation = isEnter || isRestake || isVoteLocked, ), ) } diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/utils/StakingPendingActionUtils.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/utils/StakingPendingActionUtils.kt index efda617b73..ae8f6d4cb2 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/utils/StakingPendingActionUtils.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/utils/StakingPendingActionUtils.kt @@ -5,8 +5,12 @@ import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.extensions.resourceReference import com.tangem.domain.staking.model.stakekit.PendingAction import com.tangem.domain.staking.model.stakekit.action.StakingActionType +import com.tangem.features.staking.impl.presentation.state.BalanceState +import com.tangem.lib.crypto.BlockchainUtils.isBSC import com.tangem.lib.crypto.BlockchainUtils.isSolana +import com.tangem.lib.crypto.BlockchainUtils.isTron import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.toPersistentList @Suppress("CyclomaticComplexMethod") internal fun StakingActionType?.getPendingActionTitle(): TextReference = when (this) { @@ -29,8 +33,35 @@ internal fun StakingActionType?.getPendingActionTitle(): TextReference = when (t null -> TextReference.EMPTY } -internal fun isSolanaWithdraw(networkId: String, pendingActions: ImmutableList?): Boolean { - val isSolana = isSolana(networkId) - val isWithdraw = pendingActions?.all { it.type == StakingActionType.WITHDRAW } == true - return isSolana && isWithdraw && !pendingActions.isNullOrEmpty() +internal fun isSingleAction(networkId: String, activeStake: BalanceState): Boolean { + val isSingleAction = activeStake.pendingActions.size <= 1 // Either single or none pending actions + val isComposePendingActions = isComposePendingActions(networkId, activeStake.pendingActions) + val isBscRestake = isBSC(networkId) && activeStake.pendingActions.any { + it.type == StakingActionType.RESTAKE + } + + return isSingleAction && !isBscRestake || isComposePendingActions +} + +internal fun withStubUnstakeAction(networkId: String, activeStake: BalanceState) = if (isBSC(networkId)) { + activeStake.pendingActions.plus( + PendingAction( + type = StakingActionType.UNSTAKE, + passthrough = "", + args = null, + ), + ).toPersistentList() +} else { + activeStake.pendingActions +} + +internal fun isTronStakedBalance(networkId: String, pendingAction: PendingAction?): Boolean { + return isTron(networkId) && pendingAction?.type == StakingActionType.REVOKE +} + +internal fun isComposePendingActions(networkId: String, pendingActions: ImmutableList?): Boolean { + return when { + isSolana(networkId) -> pendingActions?.any { it.type == StakingActionType.WITHDRAW } == true + else -> false + } } \ No newline at end of file diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingConfirmationContent.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingConfirmationContent.kt index 255fd36faa..ad11c31650 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingConfirmationContent.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingConfirmationContent.kt @@ -19,13 +19,13 @@ import com.tangem.core.ui.extensions.resourceReference import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemThemePreview import com.tangem.domain.staking.model.stakekit.action.StakingActionCommonType -import com.tangem.domain.staking.model.stakekit.action.StakingActionType import com.tangem.features.staking.impl.R import com.tangem.features.staking.impl.presentation.state.InnerConfirmationStakingState import com.tangem.features.staking.impl.presentation.state.StakingNotification import com.tangem.features.staking.impl.presentation.state.StakingStates import com.tangem.features.staking.impl.presentation.state.TransactionDoneState import com.tangem.features.staking.impl.presentation.state.previewdata.ConfirmationStatePreviewData +import com.tangem.features.staking.impl.presentation.state.previewdata.ValidatorStatePreviewData import com.tangem.features.staking.impl.presentation.state.stub.StakingClickIntentsStub import com.tangem.features.staking.impl.presentation.ui.block.NotificationsBlock import com.tangem.features.staking.impl.presentation.ui.block.StakingFeeBlock @@ -36,12 +36,12 @@ import com.tangem.features.staking.impl.presentation.viewmodel.StakingClickInten internal fun StakingConfirmationContent( amountState: AmountState, state: StakingStates.ConfirmationState, + validatorState: StakingStates.ValidatorState, clickIntents: StakingClickIntents, type: StakingActionCommonType, ) { if (state !is StakingStates.ConfirmationState.Data) return - val isEnterAction = type == StakingActionCommonType.ENTER - val showValidatorBlock = isEnterAction || state.pendingAction?.type == StakingActionType.VOTE_LOCKED + val isEnterAction = type == StakingActionCommonType.Enter val isTransactionSent = state.innerState == InnerConfirmationStakingState.COMPLETED val isTransactionInProgress = state.notifications.any { it is StakingNotification.Warning.TransactionInProgress } Column( @@ -67,13 +67,11 @@ internal fun StakingConfirmationContent( isEditingDisabled = !isEnterAction && state.innerState != InnerConfirmationStakingState.COMPLETED, onClick = clickIntents::onPrevClick, ) - if (showValidatorBlock) { - ValidatorBlock( - validatorState = state.validatorState, - isClickable = !isTransactionInProgress, - onClick = clickIntents::openValidators, - ) - } + ValidatorBlock( + validatorState = validatorState, + isClickable = !isTransactionInProgress, + onClick = clickIntents::openValidators, + ) StakingFeeBlock(feeState = state.feeState, isTransactionSent = isTransactionSent) NotificationsBlock(notifications = state.notifications) } @@ -88,8 +86,9 @@ private fun Preview_StakingConfirmationContent() { StakingConfirmationContent( amountState = AmountStatePreviewData.amountState, state = ConfirmationStatePreviewData.assentStakingState, + validatorState = ValidatorStatePreviewData.validatorState, clickIntents = StakingClickIntentsStub, - type = StakingActionCommonType.ENTER, + type = StakingActionCommonType.Enter, ) } } diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingScreen.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingScreen.kt index 5c2e5ceb51..21be700364 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingScreen.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingScreen.kt @@ -3,7 +3,6 @@ package com.tangem.features.staking.impl.presentation.ui import androidx.activity.compose.BackHandler import androidx.compose.animation.AnimatedContent import androidx.compose.animation.AnimatedContentTransitionScope -import androidx.compose.animation.ExperimentalAnimationApi import androidx.compose.animation.core.tween import androidx.compose.animation.togetherWith import androidx.compose.foundation.background @@ -89,15 +88,12 @@ private fun StakingAppBar(uiState: StakingUiState) { val (backIcon, click) = when (uiState.currentStep) { StakingStep.Amount, StakingStep.Confirmation, - -> { - R.drawable.ic_close_24 to uiState.clickIntents::onBackClick - } + -> R.drawable.ic_close_24 to uiState.clickIntents::onBackClick StakingStep.Validators, StakingStep.RewardsValidators, + StakingStep.RestakeValidator, StakingStep.InitialInfo, - -> { - R.drawable.ic_back_24 to uiState.clickIntents::onPrevClick - } + -> R.drawable.ic_back_24 to uiState.clickIntents::onPrevClick } AppBarWithBackButtonAndIcon( text = uiState.title.resolveReference(), @@ -109,7 +105,6 @@ private fun StakingAppBar(uiState: StakingUiState) { ) } -@OptIn(ExperimentalAnimationApi::class) @Composable private fun StakingScreenContent(uiState: StakingUiState, modifier: Modifier = Modifier) { val currentScreen = uiState.currentStep @@ -172,17 +167,16 @@ private fun StakingScreenContent(uiState: StakingUiState, modifier: Modifier = M StakingStep.Confirmation -> StakingConfirmationContent( amountState = uiState.amountState, state = uiState.confirmationState, + validatorState = uiState.validatorState, clickIntents = uiState.clickIntents, type = uiState.actionType, ) - StakingStep.Validators -> { - val confirmState = uiState.confirmationState - if (confirmState !is StakingStates.ConfirmationState.Data) return@AnimatedContent - StakingValidatorListContent( - state = confirmState.validatorState, - clickIntents = uiState.clickIntents, - ) - } + StakingStep.RestakeValidator, + StakingStep.Validators, + -> StakingValidatorListContent( + state = uiState.validatorState, + clickIntents = uiState.clickIntents, + ) } } } diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingValidatorListContent.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingValidatorListContent.kt index 9ddda86f32..692f5b3050 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingValidatorListContent.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingValidatorListContent.kt @@ -28,8 +28,8 @@ import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemThemePreview import com.tangem.core.ui.utils.BigDecimalFormatter import com.tangem.features.staking.impl.R -import com.tangem.features.staking.impl.presentation.state.ValidatorState -import com.tangem.features.staking.impl.presentation.state.previewdata.ConfirmationStatePreviewData +import com.tangem.features.staking.impl.presentation.state.StakingStates +import com.tangem.features.staking.impl.presentation.state.previewdata.ValidatorStatePreviewData import com.tangem.features.staking.impl.presentation.state.stub.StakingClickIntentsStub import com.tangem.features.staking.impl.presentation.viewmodel.StakingClickIntents import com.tangem.utils.extensions.orZero @@ -39,7 +39,7 @@ import com.tangem.utils.extensions.orZero */ @Composable internal fun StakingValidatorListContent( - state: ValidatorState, + state: StakingStates.ValidatorState, clickIntents: StakingClickIntents, modifier: Modifier = Modifier, ) { @@ -51,7 +51,7 @@ internal fun StakingValidatorListContent( .background(TangemTheme.colors.background.secondary) .padding(horizontal = TangemTheme.dimens.spacing16), ) { - if (state is ValidatorState.Content) { + if (state is StakingStates.ValidatorState.Data) { val validators = state.availableValidators items( count = validators.size, @@ -131,7 +131,7 @@ private fun RowScope.ValidatorLabel(isStrategicPartner: Boolean) { @Composable private fun StakingValidatorListContent_Preview( @PreviewParameter(StakingValidatorListContentPreviewProvider::class) - data: ValidatorState, + data: StakingStates.ValidatorState, ) { TangemThemePreview { StakingValidatorListContent( @@ -141,8 +141,8 @@ private fun StakingValidatorListContent_Preview( } } -private class StakingValidatorListContentPreviewProvider : PreviewParameterProvider { - override val values: Sequence - get() = sequenceOf(ConfirmationStatePreviewData.assentStakingState.validatorState) +private class StakingValidatorListContentPreviewProvider : PreviewParameterProvider { + override val values: Sequence + get() = sequenceOf(ValidatorStatePreviewData.validatorState) } // endregion \ No newline at end of file diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/block/ValidatorBlock.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/block/ValidatorBlock.kt index 192d1e5397..2da1751f34 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/block/ValidatorBlock.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/block/ValidatorBlock.kt @@ -15,39 +15,40 @@ import com.tangem.core.ui.extensions.* import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.utils.BigDecimalFormatter import com.tangem.features.staking.impl.R -import com.tangem.features.staking.impl.presentation.state.ValidatorState +import com.tangem.features.staking.impl.presentation.state.StakingStates import com.tangem.features.staking.impl.presentation.ui.ValidatorImagePlaceholder import com.tangem.utils.extensions.orZero @Composable -internal fun ValidatorBlock(validatorState: ValidatorState, isClickable: Boolean, onClick: () -> Unit) { +internal fun ValidatorBlock(validatorState: StakingStates.ValidatorState, isClickable: Boolean, onClick: () -> Unit) { + val state = validatorState as? StakingStates.ValidatorState.Data ?: return + if (!state.isVisibleOnConfirmation) return + Column( modifier = Modifier .fillMaxWidth() .clip(TangemTheme.shapes.roundedCornersXMedium) .background(TangemTheme.colors.background.action) .clickable( - enabled = validatorState.isClickable && isClickable, + enabled = state.isClickable && isClickable, interactionSource = remember { MutableInteractionSource() }, indication = ripple(), onClick = onClick, ), ) { - if (validatorState is ValidatorState.Content) { - InputRowImageInfo( - title = resourceReference(R.string.staking_validator), - subtitle = stringReference(validatorState.chosenValidator.name), - infoTitle = annotatedReference { - append(resourceReference(R.string.staking_details_apr).resolveReference()) - appendSpace() - appendColored( - text = BigDecimalFormatter.formatPercent(validatorState.chosenValidator.apr.orZero(), true), - color = TangemTheme.colors.text.accent, - ) - }, - imageUrl = validatorState.chosenValidator.image.orEmpty(), - onImageError = { ValidatorImagePlaceholder() }, - ) - } + InputRowImageInfo( + title = resourceReference(R.string.staking_validator), + subtitle = stringReference(state.chosenValidator.name), + infoTitle = annotatedReference { + append(resourceReference(R.string.staking_details_apr).resolveReference()) + appendSpace() + appendColored( + text = BigDecimalFormatter.formatPercent(state.chosenValidator.apr.orZero(), true), + color = TangemTheme.colors.text.accent, + ) + }, + imageUrl = state.chosenValidator.image.orEmpty(), + onImageError = { ValidatorImagePlaceholder() }, + ) } } \ No newline at end of file diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/viewmodel/StakingClickIntents.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/viewmodel/StakingClickIntents.kt index 8e55492aa8..7e23d53fc6 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/viewmodel/StakingClickIntents.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/viewmodel/StakingClickIntents.kt @@ -3,13 +3,10 @@ package com.tangem.features.staking.impl.presentation.viewmodel import com.tangem.common.ui.amountScreen.AmountScreenClickIntents import com.tangem.common.ui.bottomsheet.permission.state.ApproveType import com.tangem.common.ui.notifications.NotificationUM -import com.tangem.domain.staking.model.stakekit.PendingAction import com.tangem.domain.staking.model.stakekit.Yield -import com.tangem.domain.staking.model.stakekit.action.StakingActionCommonType import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.features.staking.impl.presentation.state.BalanceState import com.tangem.features.staking.impl.presentation.state.bottomsheet.InfoType -import kotlinx.collections.immutable.ImmutableList import java.math.BigDecimal @Suppress("TooManyFunctions") @@ -17,12 +14,7 @@ internal interface StakingClickIntents : AmountScreenClickIntents { fun onBackClick() - fun onNextClick( - actionTypeToOverwrite: StakingActionCommonType? = null, - pendingAction: PendingAction? = null, - pendingActions: ImmutableList? = null, - balanceState: BalanceState? = null, - ) + fun onNextClick(balanceState: BalanceState? = null) fun onActionClick() @@ -36,9 +28,9 @@ internal interface StakingClickIntents : AmountScreenClickIntents { fun onEnterClick() - fun getFee(pendingAction: PendingAction?, pendingActions: ImmutableList?) + fun getFee() - override fun onAmountNext() = onNextClick(actionTypeToOverwrite = null) + override fun onAmountNext() = onNextClick() fun openValidators() diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/viewmodel/StakingViewModel.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/viewmodel/StakingViewModel.kt index a6a90596ef..8e3d7ac5ee 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/viewmodel/StakingViewModel.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/viewmodel/StakingViewModel.kt @@ -63,12 +63,14 @@ import com.tangem.features.staking.impl.presentation.state.transformers.* import com.tangem.features.staking.impl.presentation.state.transformers.amount.* import com.tangem.features.staking.impl.presentation.state.transformers.approval.* import com.tangem.features.staking.impl.presentation.state.transformers.validator.ValidatorSelectChangeTransformer -import com.tangem.features.staking.impl.presentation.state.utils.isSolanaWithdraw +import com.tangem.features.staking.impl.presentation.state.utils.isSingleAction +import com.tangem.features.staking.impl.presentation.state.utils.withStubUnstakeAction import com.tangem.utils.Provider import com.tangem.utils.coroutines.* import com.tangem.utils.extensions.isSingleItem import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.persistentListOf import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.* import kotlinx.coroutines.launch @@ -185,6 +187,7 @@ internal class StakingViewModel @Inject constructor( private var stakingApproval: StakingApproval = StakingApproval.Empty private var isAmountSubtractAvailable: Boolean = false + private var isAnyTokenStaked: Boolean = false private val allowanceTaskScheduler = SingleTaskScheduler() private val transactionsInProgress: CopyOnWriteArrayList = CopyOnWriteArrayList() @@ -210,70 +213,16 @@ internal class StakingViewModel @Inject constructor( stakingStateRouter.onBackClick() } - override fun onNextClick( - actionTypeToOverwrite: StakingActionCommonType?, - pendingAction: PendingAction?, - pendingActions: ImmutableList?, - balanceState: BalanceState?, - ) { - if (actionTypeToOverwrite != null) { - stateController.update(SetActionToExecuteTransformer(actionTypeToOverwrite, pendingAction, pendingActions)) - } + override fun onNextClick(balanceState: BalanceState?) { stakingStateRouter.onNextClick() - when { - isInitState() -> { - stateController.update(SetConfirmationStateResetAssentTransformer) - stateController.update( - SetConfirmationStateLoadingTransformer( - yield = yield, - appCurrency = appCurrency, - cryptoCurrency = cryptoCurrencyStatus.currency, - ), - ) - if (balanceState != null) { - stateController.update( - SetPossiblePendingTransactionTransformer(yield, balanceState, cryptoCurrencyStatus), - ) - } - - stateController.update( - transformer = SetInitialDataStateTransformer( - clickIntents = this@StakingViewModel, - yield = yield, - isAnyTokenStaked = true, - isApprovalNeeded = stakingApproval is StakingApproval.Needed, - cryptoCurrencyStatusProvider = Provider { cryptoCurrencyStatus }, - userWalletProvider = Provider { userWallet }, - appCurrencyProvider = Provider { appCurrency }, - balancesToShowProvider = Provider { balancesToShow }, - ), - ) - onRefreshSwipe(isRefreshing = false) - } - isAssentState() -> { - getFee(pendingAction, pendingActions) - if (balanceState != null) { - stateController.update( - SetPossiblePendingTransactionTransformer(yield, balanceState, cryptoCurrencyStatus), - ) - } - val amountState = value.amountState as? AmountState.Data - if (amountState?.amountTextField?.isWarning == true) { - stateController.update( - AmountRoundToIntegerTransformer( - cryptoCurrencyStatus = cryptoCurrencyStatus, - ), - ) - } - } + if (balanceState != null && (isInitState() || isAssentState())) { + stateController.update( + SetPossiblePendingTransactionTransformer(yield, balanceState, cryptoCurrencyStatus), + ) } } - override fun onActionClick() { - handleOnNextConfirmationClick() - } - - override fun getFee(pendingAction: PendingAction?, pendingActions: ImmutableList?) { + override fun getFee() { stateController.update( SetConfirmationStateLoadingTransformer( yield = yield, @@ -283,16 +232,12 @@ internal class StakingViewModel @Inject constructor( ) viewModelScope.launch { feeLoader.getFee( - pendingAction = pendingAction, - pendingActions = pendingActions, onStakingFee = { gasEstimate -> stateController.update( SetConfirmationStateAssentTransformer( appCurrencyProvider = Provider { appCurrency }, feeCryptoCurrencyStatus = feeCryptoCurrencyStatus, fee = gasEstimate, - action = pendingAction, - actions = pendingActions, ), ) updateNotifications() @@ -326,7 +271,7 @@ internal class StakingViewModel @Inject constructor( }.saveIn(feeJobHolder) } - private fun handleOnNextConfirmationClick() { + override fun onActionClick() { if (isAssentState()) { viewModelScope.launch { stakingAnalyticSender.sendTransactionStakingClickedAnalytics(value) @@ -412,28 +357,23 @@ internal class StakingViewModel @Inject constructor( } override fun onEnterClick() { - onAmountValueChange("") // reset amount state - - // TODO refactor in [REDACTED_JIRA] - val confirmationState = value.confirmationState as? StakingStates.ConfirmationState.Data - val filteredValidator = yield.validators.filter { it.preferred } - if (filteredValidator.isEmpty()) { + if (yield.preferredValidators.isEmpty()) { stateController.updateEvent( StakingEvent.ShowAlert(StakingAlertUM.NoAvailableValidators), ) } else { - stateController.update { - value.copy( - confirmationState = confirmationState?.copy( - validatorState = ValidatorState.Content( - isClickable = true, - chosenValidator = filteredValidator[0], - availableValidators = filteredValidator, - ), - ) as StakingStates.ConfirmationState, - ) - } - onNextClick(StakingActionCommonType.ENTER) + stateController.updateAll( + SetConfirmationStateInitTransformer( + isEnter = true, + cryptoCurrencyStatus = cryptoCurrencyStatus, + stakingApproval = stakingApproval, + ), + ValidatorSelectChangeTransformer( + selectedValidator = null, + yield = yield, + ), + ) + onNextClick() } } @@ -474,7 +414,12 @@ internal class StakingViewModel @Inject constructor( validator.name, ), ) - stateController.update(ValidatorSelectChangeTransformer(validator)) + stateController.update( + ValidatorSelectChangeTransformer( + selectedValidator = validator, + yield = yield, + ), + ) } override fun openRewardsValidators() { @@ -493,50 +438,38 @@ internal class StakingViewModel @Inject constructor( token = value.cryptoCurrencySymbol, ), ) - onNextClick(actionTypeToOverwrite = StakingActionCommonType.PENDING_REWARDS) + stateController.update { + value.copy(actionType = StakingActionCommonType.Pending.Rewards) + } + stakingStateRouter.showRewardsValidators() } } override fun onActiveStake(activeStake: BalanceState) { - val isAllWithdrawActions = isSolanaWithdraw( - cryptoCurrencyStatus.currency.network.id.value, - activeStake.pendingActions, - ) - val isMultiplePendingActions = activeStake.pendingActions.size > 1 - if (isMultiplePendingActions && !isAllWithdrawActions) { + val networkId = cryptoCurrencyStatus.currency.network.id.value + if (isSingleAction(networkId, activeStake)) { + prepareForConfirmation( + pendingActions = activeStake.pendingActions, + validator = activeStake.validator, + amountValue = activeStake.cryptoValue, + ) + onNextClick(balanceState = activeStake) + } else { stateController.update( ShowActionSelectorBottomSheetTransformer( - pendingActions = activeStake.pendingActions, + pendingActions = withStubUnstakeAction(networkId, activeStake), onActionSelect = { action -> - stateController.update(ValidatorSelectChangeTransformer(activeStake.validator)) - stateController.update( - AmountChangeStateTransformer( - cryptoCurrencyStatus, - activeStake.cryptoValue, - yield, - ), - ) - onNextClick( - actionTypeToOverwrite = StakingActionCommonType.PENDING_OTHER, + prepareForConfirmation( pendingAction = action, - balanceState = activeStake, + validator = activeStake.validator, + amountValue = activeStake.cryptoValue, ) stateController.update(DismissBottomSheetStateTransformer) + onNextClick(balanceState = activeStake) }, onDismiss = { stateController.update(DismissBottomSheetStateTransformer) }, ), ) - } else { - stateController.update(ActionTypeActiveStakeTransformer(cryptoCurrencyStatus, activeStake)) - stateController.update(ValidatorSelectChangeTransformer(activeStake.validator)) - stateController.update(AmountChangeStateTransformer(cryptoCurrencyStatus, activeStake.cryptoValue, yield)) - - onNextClick( - actionTypeToOverwrite = null, - pendingAction = activeStake.pendingActions.firstOrNull(), - pendingActions = activeStake.pendingActions.takeIf { isAllWithdrawActions }, - balanceState = activeStake, - ) } } @@ -632,7 +565,7 @@ internal class StakingViewModel @Inject constructor( stakingAnalyticSender.sendTransactionApprovalAnalytics(tokenCryptoCurrency) stateController.update(SetApprovalInProgressTransformer) stateController.update(DismissBottomSheetStateTransformer) - awaitForAllowance(confirmationState.pendingAction) + awaitForAllowance() }, ) }.saveIn(approvalJobHolder) @@ -718,7 +651,7 @@ internal class StakingViewModel @Inject constructor( stateController.update(DismissStakingNotificationsStateTransformer(notification)) } - private fun awaitForAllowance(pendingAction: PendingAction?) { + private fun awaitForAllowance() { val approval = stakingApproval as? StakingApproval.Needed ?: return allowanceTaskScheduler.scheduleTask( scope = viewModelScope, @@ -737,7 +670,7 @@ internal class StakingViewModel @Inject constructor( val amount = (value.amountState as? AmountState.Data)?.amountTextField?.cryptoAmount?.value ?: error("No amount provided") if (allowance >= amount) { - getFee(pendingAction = pendingAction, pendingActions = null) + getFee() allowanceTaskScheduler.cancelTask() } }, @@ -775,7 +708,7 @@ internal class StakingViewModel @Inject constructor( val cardInfo = getCardInfoUseCase(userWallet.scanResponse).getOrElse { error("CardInfo must be not null") } val amountState = uiState.value.amountState as? AmountState.Data val confirmationState = uiState.value.confirmationState as? StakingStates.ConfirmationState.Data - val validatorState = confirmationState?.validatorState as? ValidatorState.Content + val validatorState = uiState.value.validatorState as? StakingStates.ValidatorState.Data val feeState = confirmationState?.feeState as? FeeState.Content val validator = validatorState?.chosenValidator @@ -817,6 +750,10 @@ internal class StakingViewModel @Inject constructor( stakingApproval = isApproveNeededUseCase(cryptoCurrencyStatus.currency).getOrElse { StakingApproval.Empty } } + private suspend fun setupIsAnyTokenStaked() { + isAnyTokenStaked = isAnyTokenStakedUseCase(userWalletId).getOrNull() ?: false + } + private fun subscribeOnCurrencyStatusUpdates() { getUserWalletUseCase(userWalletId).fold( ifRight = { wallet -> @@ -849,23 +786,11 @@ internal class StakingViewModel @Inject constructor( feeCryptoCurrencyStatus = getFeePaidCryptoCurrencyStatusSyncUseCase(userWalletId, status).getOrNull() cryptoCurrencyStatus = status - val isAnyTokenStaked = isAnyTokenStakedUseCase(userWalletId).getOrNull() ?: false setupApprovalNeeded() + setupIsAnyTokenStaked() checkIfSubtractAvailable() - - stateController.update( - transformer = SetInitialDataStateTransformer( - clickIntents = this@StakingViewModel, - yield = yield, - isAnyTokenStaked = isAnyTokenStaked, - isApprovalNeeded = stakingApproval is StakingApproval.Needed, - cryptoCurrencyStatusProvider = Provider { cryptoCurrencyStatus }, - userWalletProvider = Provider { userWallet }, - appCurrencyProvider = Provider { appCurrency }, - balancesToShowProvider = Provider { balancesToShow }, - ), - ) + subscribeOnStepChanges() }, ifLeft = { error -> Timber.e(error.toString()) @@ -900,6 +825,69 @@ internal class StakingViewModel @Inject constructor( .launchIn(viewModelScope) } + private fun subscribeOnStepChanges() { + uiState + .distinctUntilChangedBy { it.currentStep } + .onEach { + when { + isInitState() -> { + stateController.updateAll( + SetInitialDataStateTransformer( + clickIntents = this@StakingViewModel, + yield = yield, + isAnyTokenStaked = isAnyTokenStaked, + cryptoCurrencyStatusProvider = Provider { cryptoCurrencyStatus }, + userWalletProvider = Provider { userWallet }, + appCurrencyProvider = Provider { appCurrency }, + balancesToShowProvider = Provider { balancesToShow }, + ), + SetConfirmationStateEmptyTransformer, + ) + onRefreshSwipe(isRefreshing = false) + } + isAssentState() -> { + getFee() + val amountState = value.amountState as? AmountState.Data + if (amountState?.amountTextField?.isWarning == true) { + stateController.update( + AmountRoundToIntegerTransformer( + cryptoCurrencyStatus = cryptoCurrencyStatus, + ), + ) + } + } + } + } + .flowOn(dispatchers.main) + .launchIn(viewModelScope) + } + + private fun prepareForConfirmation( + pendingActions: ImmutableList = persistentListOf(), + pendingAction: PendingAction? = pendingActions.firstOrNull(), + validator: Yield.Validator?, + amountValue: String, + ) { + stateController.updateAll( + SetConfirmationStateInitTransformer( + isEnter = false, + cryptoCurrencyStatus = cryptoCurrencyStatus, + stakingApproval = stakingApproval, + pendingActions = pendingActions, + pendingAction = pendingAction, + ), + ValidatorSelectChangeTransformer( + selectedValidator = validator, + yield = yield, + ), + AmountChangeStateTransformer( + cryptoCurrencyStatus = cryptoCurrencyStatus, + value = amountValue, + yield = yield, + ), + ) + } + private fun isAssentState(): Boolean { return value.currentStep == StakingStep.Confirmation && (value.confirmationState as? StakingStates.ConfirmationState.Data)?.innerState == diff --git a/libs/crypto/src/main/java/com/tangem/lib/crypto/BlockchainUtils.kt b/libs/crypto/src/main/java/com/tangem/lib/crypto/BlockchainUtils.kt index 1c340dabc3..fe67dcf443 100644 --- a/libs/crypto/src/main/java/com/tangem/lib/crypto/BlockchainUtils.kt +++ b/libs/crypto/src/main/java/com/tangem/lib/crypto/BlockchainUtils.kt @@ -88,6 +88,11 @@ object BlockchainUtils { return blockchain == Blockchain.Cosmos || blockchain == Blockchain.CosmosTestnet } + fun isBSC(networkId: String): Boolean { + val blockchain = Blockchain.fromId(networkId) + return blockchain == Blockchain.BSC || blockchain == Blockchain.BSCTestnet + } + data class BlockchainInfo( val blockchainId: String, val name: String, From e77c29223021b2a75be0248bfdec76e0cd8e07b8 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 15 Oct 2024 18:50:21 +0300 Subject: [PATCH 003/106] Updated on 2026-08-14 --- .../models/response/model/YieldDTO.kt | 62 ++++++++--------- .../com/tangem/utils/converter/Converter.kt | 15 ++++ .../data/staking/DefaultStakingRepository.kt | 9 +-- .../data/staking/converters/YieldConverter.kt | 69 ++++++++++--------- 4 files changed, 87 insertions(+), 68 deletions(-) diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/stakekit/models/response/model/YieldDTO.kt b/core/datasource/src/main/java/com/tangem/datasource/api/stakekit/models/response/model/YieldDTO.kt index 330df3c666..951d60ddf7 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/stakekit/models/response/model/YieldDTO.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/stakekit/models/response/model/YieldDTO.kt @@ -9,31 +9,31 @@ data class YieldDTO( @Json(name = "id") val id: String, @Json(name = "token") - val token: TokenDTO, + val token: TokenDTO?, @Json(name = "tokens") - val tokens: List, + val tokens: List?, @Json(name = "args") - val args: ArgsDTO, + val args: ArgsDTO?, @Json(name = "status") - val status: StatusDTO, + val status: StatusDTO?, @Json(name = "apy") - val apy: BigDecimal, + val apy: BigDecimal?, @Json(name = "rewardRate") - val rewardRate: Double, + val rewardRate: Double?, @Json(name = "rewardType") - val rewardType: RewardTypeDTO, + val rewardType: RewardTypeDTO?, @Json(name = "metadata") - val metadata: MetadataDTO, + val metadata: MetadataDTO?, @Json(name = "validators") - val validators: List, + val validators: List?, @Json(name = "isAvailable") - val isAvailable: Boolean, + val isAvailable: Boolean?, ) { @JsonClass(generateAdapter = true) data class StatusDTO( @Json(name = "enter") - val enter: Boolean, + val enter: Boolean?, @Json(name = "exit") val exit: Boolean?, ) @@ -41,21 +41,21 @@ data class YieldDTO( @JsonClass(generateAdapter = true) data class ArgsDTO( @Json(name = "enter") - val enter: Enter, + val enter: Enter?, @Json(name = "exit") val exit: Enter?, ) { @JsonClass(generateAdapter = true) data class Enter( @Json(name = "addresses") - val addresses: Addresses, + val addresses: Addresses?, @Json(name = "args") - val args: Map, + val args: Map?, ) { @JsonClass(generateAdapter = true) data class Addresses( @Json(name = "address") - val address: AddressArgumentDTO, + val address: AddressArgumentDTO?, @Json(name = "additionalAddresses") val additionalAddresses: Map? = null, ) @@ -65,7 +65,7 @@ data class YieldDTO( @JsonClass(generateAdapter = true) data class ValidatorDTO( @Json(name = "address") - val address: String, + val address: String?, @Json(name = "status") val status: ValidatorStatusDTO, @Json(name = "name") @@ -106,51 +106,51 @@ data class YieldDTO( @JsonClass(generateAdapter = true) data class MetadataDTO( @Json(name = "name") - val name: String, + val name: String?, @Json(name = "logoURI") - val logoUri: String, + val logoUri: String?, @Json(name = "description") - val description: String, + val description: String?, @Json(name = "documentation") val documentation: String?, @Json(name = "gasFeeToken") - val gasFeeTokenDTO: TokenDTO, + val gasFeeTokenDTO: TokenDTO?, @Json(name = "token") - val tokenDTO: TokenDTO, + val tokenDTO: TokenDTO?, @Json(name = "tokens") - val tokensDTO: List, + val tokensDTO: List?, @Json(name = "type") - val type: String, + val type: String?, @Json(name = "rewardSchedule") - val rewardSchedule: RewardScheduleDTO, + val rewardSchedule: RewardScheduleDTO?, @Json(name = "cooldownPeriod") val cooldownPeriod: PeriodDTO?, @Json(name = "warmupPeriod") - val warmupPeriod: PeriodDTO, + val warmupPeriod: PeriodDTO?, @Json(name = "rewardClaiming") - val rewardClaiming: RewardClaimingDTO, + val rewardClaiming: RewardClaimingDTO?, @Json(name = "defaultValidator") val defaultValidator: String?, @Json(name = "minimumStake") val minimumStake: Int?, @Json(name = "supportsMultipleValidators") - val supportsMultipleValidators: Boolean, + val supportsMultipleValidators: Boolean?, @Json(name = "revshare") - val revshare: EnabledDTO, + val revshare: EnabledDTO?, @Json(name = "fee") - val fee: EnabledDTO, + val fee: EnabledDTO?, ) { @JsonClass(generateAdapter = true) data class PeriodDTO( @Json(name = "days") - val days: Int, + val days: Int?, ) @JsonClass(generateAdapter = true) data class EnabledDTO( @Json(name = "enabled") - val enabled: Boolean, + val enabled: Boolean?, ) enum class RewardScheduleDTO { diff --git a/core/utils/src/main/java/com/tangem/utils/converter/Converter.kt b/core/utils/src/main/java/com/tangem/utils/converter/Converter.kt index b818478a45..603ebf1fcd 100644 --- a/core/utils/src/main/java/com/tangem/utils/converter/Converter.kt +++ b/core/utils/src/main/java/com/tangem/utils/converter/Converter.kt @@ -11,4 +11,19 @@ interface Converter { fun convertSet(input: Collection): Set { return input.mapTo(hashSetOf(), ::convert) } + + fun convertListIgnoreErrors(input: Collection, onError: ((Throwable) -> Unit)? = null): List { + return input.mapNotNull { + try { + convert(it) + } catch (throwable: Throwable) { + onError?.invoke(throwable) + null + } + } + } + + fun T?.asMandatory(name: String): T { + return this ?: error("$name must not be null") + } } \ No newline at end of file diff --git a/data/staking/src/main/java/com/tangem/data/staking/DefaultStakingRepository.kt b/data/staking/src/main/java/com/tangem/data/staking/DefaultStakingRepository.kt index 729477a5cc..71bd21a01e 100644 --- a/data/staking/src/main/java/com/tangem/data/staking/DefaultStakingRepository.kt +++ b/data/staking/src/main/java/com/tangem/data/staking/DefaultStakingRepository.kt @@ -126,7 +126,7 @@ internal class DefaultStakingRepository( val stakingTokensWithYields = stakeKitApi.getEnabledYields(preferredValidatorsOnly = false) .getOrThrow() - stakingYieldsStore.store(stakingTokensWithYields.data.filter { it.isAvailable }) + stakingYieldsStore.store(stakingTokensWithYields.data.filter { it.isAvailable ?: false }) }, ) } @@ -579,9 +579,10 @@ internal class DefaultStakingRepository( } private fun getEnabledYields(): List { - return stakingYieldsStore - .get() - .map { yieldConverter.convert(it) } + return yieldConverter.convertListIgnoreErrors( + input = stakingYieldsStore.get(), + onError = { Timber.e("Error converting enabled yields list: $it") }, + ) } private fun getBalanceRequestData(address: String, integrationId: String): YieldBalanceRequestBody { diff --git a/data/staking/src/main/java/com/tangem/data/staking/converters/YieldConverter.kt b/data/staking/src/main/java/com/tangem/data/staking/converters/YieldConverter.kt index 8489a8772d..7ed9eb23e3 100644 --- a/data/staking/src/main/java/com/tangem/data/staking/converters/YieldConverter.kt +++ b/data/staking/src/main/java/com/tangem/data/staking/converters/YieldConverter.kt @@ -17,37 +17,37 @@ class YieldConverter( override fun convert(value: YieldDTO): Yield { return Yield( - id = value.id, - token = tokenConverter.convert(value.token), - tokens = value.tokens.map { tokenConverter.convert(it) }, - args = convertArgs(value.args), - status = convertStatus(value.status), - apy = value.apy, - rewardRate = value.rewardRate, - rewardType = convertRewardType(value.rewardType), - metadata = convertMetadata(value.metadata), - validators = value.validators + id = value.id.asMandatory("id"), + token = tokenConverter.convert(value.token.asMandatory("token")), + tokens = value.tokens.asMandatory("tokens").map { tokenConverter.convert(it) }, + args = convertArgs(value.args.asMandatory("args")), + status = convertStatus(value.status.asMandatory("status")), + apy = value.apy.asMandatory("apy"), + rewardRate = value.rewardRate.asMandatory("rewardRate"), + rewardType = convertRewardType(value.rewardType.asMandatory("rewardType")), + metadata = convertMetadata(value.metadata.asMandatory("metadata")), + validators = value.validators.asMandatory("validators") .asSequence() .filter { it.status == ValidatorStatusDTO.ACTIVE } .map { convertValidator(it) } .sortedByDescending { it.isStrategicPartner } .sortedByDescending { it.apr } .toImmutableList(), - isAvailable = value.isAvailable, + isAvailable = value.isAvailable.asMandatory("isAvailable"), ) } private fun convertArgs(argsDTO: YieldDTO.ArgsDTO): Yield.Args { return Yield.Args( - enter = convertEnter(argsDTO.enter), + enter = convertEnter(argsDTO.enter.asMandatory("enter")), exit = argsDTO.exit?.let { convertEnter(it) }, ) } private fun convertEnter(enterDTO: YieldDTO.ArgsDTO.Enter): Yield.Args.Enter { return Yield.Args.Enter( - addresses = convertAddresses(enterDTO.addresses), - args = enterDTO.args + addresses = convertAddresses(enterDTO.addresses.asMandatory("addresses")), + args = enterDTO.args.asMandatory("args") .mapKeys { convertArgType(it.key) } .mapValues { convertAddressArgument(it.value) }, ) @@ -55,7 +55,7 @@ class YieldConverter( private fun convertAddresses(addressesDTO: YieldDTO.ArgsDTO.Enter.Addresses): Yield.Args.Enter.Addresses { return Yield.Args.Enter.Addresses( - address = convertAddressArgument(addressesDTO.address), + address = convertAddressArgument(addressesDTO.address.asMandatory("address")), additionalAddresses = addressesDTO.additionalAddresses ?.mapKeys { convertArgType(it.key) } ?.mapValues { convertAddressArgument(it.value) }, @@ -73,48 +73,51 @@ class YieldConverter( private fun convertStatus(statusDTO: YieldDTO.StatusDTO): Yield.Status { return Yield.Status( - enter = statusDTO.enter, + enter = statusDTO.enter.asMandatory("enter"), exit = statusDTO.exit, ) } private fun convertMetadata(metadataDTO: YieldDTO.MetadataDTO): Yield.Metadata { return Yield.Metadata( - name = metadataDTO.name, - logoUri = metadataDTO.logoUri, - description = metadataDTO.description, + name = metadataDTO.name.asMandatory("name"), + logoUri = metadataDTO.logoUri.asMandatory("logoUri"), + description = metadataDTO.description.asMandatory("description"), documentation = metadataDTO.documentation, - gasFeeToken = tokenConverter.convert(metadataDTO.gasFeeTokenDTO), - token = tokenConverter.convert(metadataDTO.tokenDTO), - tokens = metadataDTO.tokensDTO.map { tokenConverter.convert(it) }, - type = metadataDTO.type, - rewardSchedule = convertRewardSchedule(metadataDTO.rewardSchedule), + gasFeeToken = tokenConverter.convert(metadataDTO.gasFeeTokenDTO.asMandatory("gasFeeTokenDTO")), + token = tokenConverter.convert(metadataDTO.tokenDTO.asMandatory("tokenDTO")), + tokens = metadataDTO.tokensDTO.asMandatory("tokensDTO").map { tokenConverter.convert(it) }, + type = metadataDTO.type.asMandatory("type"), + rewardSchedule = convertRewardSchedule(metadataDTO.rewardSchedule.asMandatory("rewardSchedule")), cooldownPeriod = metadataDTO.cooldownPeriod?.let { convertPeriod(it) }, - warmupPeriod = convertPeriod(metadataDTO.warmupPeriod), - rewardClaiming = convertRewardClaiming(metadataDTO.rewardClaiming), + warmupPeriod = convertPeriod(metadataDTO.warmupPeriod.asMandatory("warmupPeriod")), + rewardClaiming = convertRewardClaiming(metadataDTO.rewardClaiming.asMandatory("rewardClaiming")), defaultValidator = metadataDTO.defaultValidator, minimumStake = metadataDTO.minimumStake, - supportsMultipleValidators = metadataDTO.supportsMultipleValidators, - revshare = convertEnabled(metadataDTO.revshare), - fee = convertEnabled(metadataDTO.fee), + supportsMultipleValidators = metadataDTO.supportsMultipleValidators.asMandatory( + "supportsMultipleValidators", + ), + revshare = convertEnabled(metadataDTO.revshare.asMandatory("revshare")), + fee = convertEnabled(metadataDTO.fee.asMandatory("fee")), ) } private fun convertPeriod(periodDTO: YieldDTO.MetadataDTO.PeriodDTO): Yield.Metadata.Period { return Yield.Metadata.Period( - days = periodDTO.days, + days = periodDTO.days.asMandatory("days"), ) } private fun convertEnabled(enabledDTO: YieldDTO.MetadataDTO.EnabledDTO): Yield.Metadata.Enabled { return Yield.Metadata.Enabled( - enabled = enabledDTO.enabled, + enabled = enabledDTO.enabled.asMandatory("enabled"), ) } private fun convertValidator(validatorDTO: YieldDTO.ValidatorDTO): Yield.Validator { + val address = validatorDTO.address.asMandatory("address") return Yield.Validator( - address = validatorDTO.address, + address = address, status = convertValidatorStatus(validatorDTO.status), name = validatorDTO.name, image = validatorDTO.image, @@ -124,7 +127,7 @@ class YieldConverter( stakedBalance = validatorDTO.stakedBalance, votingPower = validatorDTO.votingPower, preferred = validatorDTO.preferred, - isStrategicPartner = isStrategicPartner(validatorDTO.address), + isStrategicPartner = isStrategicPartner(address), ) } From 42806a4d7c530cb7e1e38db5a9fee217a1d4c3e9 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 16 Oct 2024 11:59:34 +0300 Subject: [PATCH 004/106] Updated on 2026-08-14 --- .../kotlin/com/tangem/common/BaseTestCase.kt | 22 +++++++++++++++++++ .../com/tangem/tests/DetailsScreenTest.kt | 3 --- .../kotlin/com/tangem/tests/StoriesTest.kt | 4 +++- .../DefaultBlockchainSDKFactory.kt | 6 +---- 4 files changed, 26 insertions(+), 9 deletions(-) diff --git a/app/src/androidTest/kotlin/com/tangem/common/BaseTestCase.kt b/app/src/androidTest/kotlin/com/tangem/common/BaseTestCase.kt index 4b3278bc9f..3ff0cba81e 100644 --- a/app/src/androidTest/kotlin/com/tangem/common/BaseTestCase.kt +++ b/app/src/androidTest/kotlin/com/tangem/common/BaseTestCase.kt @@ -1,16 +1,22 @@ package com.tangem.common import android.Manifest +import android.content.Context +import android.util.Log import androidx.compose.ui.test.junit4.createAndroidComposeRule import androidx.test.espresso.intent.Intents import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.platform.app.InstrumentationRegistry import androidx.test.rule.GrantPermissionRule import com.kaspersky.components.composesupport.config.withComposeSupport import com.kaspersky.kaspresso.kaspresso.Kaspresso import com.kaspersky.kaspresso.testcases.api.testcase.TestCase +import com.tangem.datasource.local.preferences.AppPreferencesStore import com.tangem.tap.MainActivity import com.tangem.tap.domain.sdk.TangemSdkManager import dagger.hilt.android.testing.HiltAndroidRule +import kotlinx.coroutines.delay +import kotlinx.coroutines.runBlocking import org.junit.Rule import org.junit.rules.RuleChain import org.junit.runner.RunWith @@ -24,6 +30,9 @@ abstract class BaseTestCase : TestCase( @Inject lateinit var tangemSdkManager: TangemSdkManager + @Inject + lateinit var appPreferencesStore: AppPreferencesStore + @get:Rule open val composeTestRule = createAndroidComposeRule() @@ -48,9 +57,22 @@ abstract class BaseTestCase : TestCase( hiltRule.inject() Intents.init() additionalBeforeSection() + + runBlocking { + delay(INIT_DELAY) + } + }.after { + runBlocking { + appPreferencesStore.editData { prefs -> prefs.clear() } + } + additionalAfterSection() Intents.release() } + companion object { + private const val INIT_DELAY = 1000L + } + } \ No newline at end of file diff --git a/app/src/androidTest/kotlin/com/tangem/tests/DetailsScreenTest.kt b/app/src/androidTest/kotlin/com/tangem/tests/DetailsScreenTest.kt index 8c4bd6d3c9..dd86a046cc 100644 --- a/app/src/androidTest/kotlin/com/tangem/tests/DetailsScreenTest.kt +++ b/app/src/androidTest/kotlin/com/tangem/tests/DetailsScreenTest.kt @@ -143,9 +143,6 @@ class DetailsScreenTest : BaseTestCase() { } } ComposeScreen.onComposeScreen(composeTestRule) { - step("Assert Link more cards button does not exist") { - linkMoreCardsButton.assertIsNotDisplayed() - } step("Assert Card Settings button is visible") { cardSettingsButton.assertIsDisplayed() } diff --git a/app/src/androidTest/kotlin/com/tangem/tests/StoriesTest.kt b/app/src/androidTest/kotlin/com/tangem/tests/StoriesTest.kt index 9eb88b0075..eab4389f6b 100644 --- a/app/src/androidTest/kotlin/com/tangem/tests/StoriesTest.kt +++ b/app/src/androidTest/kotlin/com/tangem/tests/StoriesTest.kt @@ -1,6 +1,7 @@ package com.tangem.tests import android.content.Intent.ACTION_VIEW +import androidx.test.espresso.intent.matcher.UriMatchers import com.tangem.common.BaseTestCase import com.tangem.common.extensions.clickWithAssertion import com.tangem.screens.DisclaimerTestScreen @@ -9,6 +10,7 @@ import com.tangem.tap.features.home.redux.HomeMiddleware.NEW_BUY_WALLET_URL import dagger.hilt.android.testing.HiltAndroidTest import io.github.kakaocup.compose.node.element.ComposeScreen import io.github.kakaocup.kakao.intent.KIntent +import org.hamcrest.Matchers import org.junit.Test @HiltAndroidTest @@ -29,7 +31,7 @@ class StoriesTest : BaseTestCase() { step("Assert: browser opened") { val expectedIntent = KIntent { hasAction(ACTION_VIEW) - hasData(NEW_BUY_WALLET_URL) + hasData { toString().startsWith(NEW_BUY_WALLET_URL) } } expectedIntent.intended() device.uiDevice.pressBack() diff --git a/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/DefaultBlockchainSDKFactory.kt b/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/DefaultBlockchainSDKFactory.kt index 23379f7e5b..9e5dbd53c8 100644 --- a/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/DefaultBlockchainSDKFactory.kt +++ b/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/DefaultBlockchainSDKFactory.kt @@ -39,10 +39,6 @@ internal class DefaultBlockchainSDKFactory( private val mainScope = CoroutineScope(dispatchers.main) private val walletManagerFactory: Flow = createWalletManagerFactory() - // TODO: [REDACTED_JIRA] - // private val walletManagerFactory: Flow by lazy(LazyThreadSafetyMode.NONE) { - // createWalletManagerFactory() - // } override suspend fun init() { coroutineScope { @@ -59,7 +55,7 @@ internal class DefaultBlockchainSDKFactory( // flow3 = subscribe on feature toggles changes, TODO: [REDACTED_JIRA] transform = walletManagerFactoryCreator::create, ) - .stateIn(scope = mainScope, started = SharingStarted.Eagerly, initialValue = null) + .stateIn(scope = mainScope, started = SharingStarted.Lazily, initialValue = null) } private fun CoroutineScope.updateBlockchainProviderTypes() { From c6a5780867f9618fe30a7316789d622c6ee0b224 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 16 Oct 2024 12:18:25 +0500 Subject: [PATCH 005/106] Updated on 2026-08-14 --- app/src/main/assets/testnet_tokens.json | 10 ++++++++++ .../core/ui/extensions/BlockchainIcons.kt | 5 +++++ .../ui/src/main/res/drawable/ic_casper_22.xml | 9 +++++++++ .../src/main/res/drawable/img_casper_22.xml | 19 +++++++++++++++++++ .../tangem/blockchainsdk/utils/Blockchain.kt | 10 ++++++---- 5 files changed, 49 insertions(+), 4 deletions(-) create mode 100644 core/ui/src/main/res/drawable/ic_casper_22.xml create mode 100644 core/ui/src/main/res/drawable/img_casper_22.xml diff --git a/app/src/main/assets/testnet_tokens.json b/app/src/main/assets/testnet_tokens.json index 3badefc007..30fa60f3c4 100644 --- a/app/src/main/assets/testnet_tokens.json +++ b/app/src/main/assets/testnet_tokens.json @@ -748,6 +748,16 @@ "networkId": "core/test" } ] + }, + { + "id": "casper-network", + "name": "Casper", + "symbol": "CSPR", + "networks": [ + { + "networkId": "casper-network/test" + } + ] } ] } diff --git a/core/ui/src/main/java/com/tangem/core/ui/extensions/BlockchainIcons.kt b/core/ui/src/main/java/com/tangem/core/ui/extensions/BlockchainIcons.kt index 9c460e02a1..1270dd2741 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/extensions/BlockchainIcons.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/extensions/BlockchainIcons.kt @@ -79,6 +79,7 @@ fun getActiveIconRes(blockchainId: String): Int { "energy-web-chain", "energy-web-chain/test" -> R.drawable.img_energy_web_22 "energy-web-x", "energy-web-x/test" -> R.drawable.img_energy_web_22 "core", "core/test" -> R.drawable.img_core_22 + "casper-network", "casper-network/test" -> R.drawable.img_casper_22 else -> R.drawable.ic_alert_24 } } @@ -159,6 +160,7 @@ fun getActiveIconResByNetworkId(networkId: String): Int { "energy-web-chain", "energy-web-chain/test" -> R.drawable.img_energy_web_22 "energy-web-x", "energy-web-x/test" -> R.drawable.img_energy_web_22 "core", "core/test" -> R.drawable.img_core_22 + "casper-network", "casper-network/test" -> R.drawable.img_casper_22 else -> R.drawable.ic_alert_24 } } @@ -236,6 +238,7 @@ fun getActiveIconResByCoinId(coinId: String): Int { "energy-web-chain", "energy-web-chain/test" -> R.drawable.img_energy_web_22 "energy-web-x", "energy-web-x/test" -> R.drawable.img_energy_web_22 "core", "core/test" -> R.drawable.img_core_22 + "casper-network" -> R.drawable.img_casper_22 else -> R.drawable.ic_alert_24 } } @@ -316,6 +319,7 @@ fun getGreyedOutIconRes(blockchainId: String): Int { "energy-web-chain", "energy-web-chain/test" -> R.drawable.ic_energy_web_22 "energy-web-x", "energy-web-x/test" -> R.drawable.ic_energy_web_22 "core", "core/test" -> R.drawable.ic_core_22 + "casper-network", "casper-network/test" -> R.drawable.ic_casper_22 else -> R.drawable.ic_alert_24 } } @@ -396,6 +400,7 @@ fun getGreyedOutIconResByNetworkId(networkId: String): Int { "energy-web-chain", "energy-web-chain/test" -> R.drawable.ic_energy_web_22 "energy-web-x", "energy-web-x/test" -> R.drawable.ic_energy_web_22 "core", "core/test" -> R.drawable.ic_core_22 + "casper-network", "casper-network/test" -> R.drawable.ic_casper_22 else -> R.drawable.ic_alert_24 } } \ No newline at end of file diff --git a/core/ui/src/main/res/drawable/ic_casper_22.xml b/core/ui/src/main/res/drawable/ic_casper_22.xml new file mode 100644 index 0000000000..e2abfc22ea --- /dev/null +++ b/core/ui/src/main/res/drawable/ic_casper_22.xml @@ -0,0 +1,9 @@ + + + diff --git a/core/ui/src/main/res/drawable/img_casper_22.xml b/core/ui/src/main/res/drawable/img_casper_22.xml new file mode 100644 index 0000000000..ade71437f7 --- /dev/null +++ b/core/ui/src/main/res/drawable/img_casper_22.xml @@ -0,0 +1,19 @@ + + + + + + + + diff --git a/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/utils/Blockchain.kt b/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/utils/Blockchain.kt index c8e9fca637..3132bfa610 100644 --- a/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/utils/Blockchain.kt +++ b/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/utils/Blockchain.kt @@ -136,6 +136,8 @@ fun Blockchain.Companion.fromNetworkId(networkId: String): Blockchain? { "add_later_test" -> Blockchain.CasperTestnet "core" -> Blockchain.Core "core/test" -> Blockchain.CoreTestnet + "casper-network" -> Blockchain.Casper + "casper-network/test" -> Blockchain.CasperTestnet else -> null } } @@ -269,8 +271,8 @@ fun Blockchain.toNetworkId(): String { Blockchain.EnergyWebChainTestnet -> "energy-web-chain/test" Blockchain.EnergyWebX -> "energy-web-x" Blockchain.EnergyWebXTestnet -> "energy-web-x/test" - Blockchain.Casper -> "add_later" - Blockchain.CasperTestnet -> "add_later_test" + Blockchain.Casper -> "casper-network" + Blockchain.CasperTestnet -> "casper-network/test" Blockchain.Core -> "core" Blockchain.CoreTestnet -> "core/test" } @@ -360,8 +362,7 @@ fun Blockchain.toCoinId(): String { Blockchain.Sui, Blockchain.SuiTestnet -> "sui" Blockchain.EnergyWebChain, Blockchain.EnergyWebChainTestnet -> "energy-web-token" Blockchain.EnergyWebX, Blockchain.EnergyWebXTestnet -> "energy-web-token" - Blockchain.Casper -> "add_later" - Blockchain.CasperTestnet -> "add_later_test" + Blockchain.Casper, Blockchain.CasperTestnet -> "casper-network" Blockchain.Core, Blockchain.CoreTestnet -> "coredaoorg" } } @@ -386,6 +387,7 @@ fun Blockchain.amountToCreateAccount(token: Token? = null): BigDecimal? { Blockchain.Near, Blockchain.NearTestnet -> 0.00182.toBigDecimal() Blockchain.Aptos, Blockchain.AptosTestnet, Blockchain.Filecoin, + Blockchain.Casper, Blockchain.CasperTestnet, -> BigDecimal.ZERO else -> null } From ca2c82d15eb0a34b68cd94350c9dfb29a701d642 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 16 Oct 2024 18:22:06 +0500 Subject: [PATCH 006/106] Updated on 2026-08-14 --- .../datasource/api/onramp/MockedOnrampApi.kt | 178 ++++++++++++++++++ .../com/tangem/datasource/di/NetworkModule.kt | 33 ++-- 2 files changed, 196 insertions(+), 15 deletions(-) create mode 100644 core/datasource/src/main/java/com/tangem/datasource/api/onramp/MockedOnrampApi.kt diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/onramp/MockedOnrampApi.kt b/core/datasource/src/main/java/com/tangem/datasource/api/onramp/MockedOnrampApi.kt new file mode 100644 index 0000000000..a960766de8 --- /dev/null +++ b/core/datasource/src/main/java/com/tangem/datasource/api/onramp/MockedOnrampApi.kt @@ -0,0 +1,178 @@ +package com.tangem.datasource.api.onramp + +import com.tangem.datasource.api.common.response.ApiResponse +import com.tangem.datasource.api.onramp.models.common.OnrampDestinationDTO +import com.tangem.datasource.api.onramp.models.request.OnrampPairsRequest +import com.tangem.datasource.api.onramp.models.response.OnrampDataResponse +import com.tangem.datasource.api.onramp.models.response.OnrampQuoteResponse +import com.tangem.datasource.api.onramp.models.response.OnrampStatusResponse +import com.tangem.datasource.api.onramp.models.response.model.OnrampCountryDTO +import com.tangem.datasource.api.onramp.models.response.model.OnrampCurrencyDTO +import com.tangem.datasource.api.onramp.models.response.model.OnrampPairDTO +import com.tangem.datasource.api.onramp.models.response.model.PaymentMethodDTO + +internal class MockedOnrampApi : OnrampApi { + override suspend fun getCurrencies(): ApiResponse> = ApiResponse.Success( + COUNTRIES.map(OnrampCountryDTO::defaultCurrency), + ) + + override suspend fun getCountries(): ApiResponse> = ApiResponse.Success(COUNTRIES + RUSSIA) + + override suspend fun getCountryByIp(): ApiResponse = ApiResponse.Success(RUSSIA) + + override suspend fun getPaymentMethods(): ApiResponse> = ApiResponse.Success( + listOf( + PaymentMethodDTO(id = "google", name = "Google Play", image = ""), + PaymentMethodDTO(id = "apple", name = "Apple Pay", image = ""), + PaymentMethodDTO(id = "card", name = "Card", image = ""), + ), + ) + + override suspend fun getPairs(body: OnrampPairsRequest): ApiResponse> = ApiResponse.Success( + listOf( + OnrampPairDTO( + fromCurrencyCode = "USD", + to = OnrampDestinationDTO(contractAddress = "0xcontract_address", network = "ethereum"), + providers = listOf(), + ), + ), + ) + + override suspend fun getQuote( + fromCurrencyCode: String, + toContractAddress: String, + toNetwork: String, + paymentMethod: String, + countryCode: String, + fromAmount: String, + toDecimals: Int, + providerId: String, + ): ApiResponse { + TODO("Not yet implemented") + } + + override suspend fun getData( + fromCurrencyCode: String, + toContractAddress: String, + toNetwork: String, + paymentMethod: String, + countryCode: String, + fromAmount: String, + toDecimals: Int, + providerId: String, + toAddress: String, + redirectUrl: String, + language: String?, + theme: String?, + requestId: String, + ): ApiResponse { + TODO("Not yet implemented") + } + + override suspend fun getStatus(txId: String): ApiResponse { + TODO("Not yet implemented") + } + + private companion object { + private val RUSSIA = OnrampCountryDTO( + name = "Russia", + code = "RU", + image = "https://hatscripts.github.io/circle-flags/flags/ru.svg", + alpha3 = "RUS", + continent = "", + defaultCurrency = OnrampCurrencyDTO( + name = "Russian ruble", + code = "RUB", + image = "https://hatscripts.github.io/circle-flags/flags/ru.svg", + precision = 2, + ), + onrampAvailable = false, + ) + private val COUNTRIES = listOf( + OnrampCountryDTO( + name = "United States of America", + code = "USA", + image = "https://hatscripts.github.io/circle-flags/flags/us.svg", + alpha3 = "USA", + continent = "", + defaultCurrency = OnrampCurrencyDTO( + name = "US Dollar", + code = "USD", + image = "https://hatscripts.github.io/circle-flags/flags/us.svg", + precision = 2, + ), + onrampAvailable = true, + ), + OnrampCountryDTO( + name = "Europe Union", + code = "EU", + image = "https://hatscripts.github.io/circle-flags/flags/eu.svg", + alpha3 = "EUR", + continent = "", + defaultCurrency = OnrampCurrencyDTO( + name = "Euro", + code = "EUR", + image = "https://hatscripts.github.io/circle-flags/flags/eu.svg", + precision = 2, + ), + onrampAvailable = true, + ), + OnrampCountryDTO( + name = "Great Britain", + code = "GB", + image = "https://hatscripts.github.io/circle-flags/flags/gb.svg", + alpha3 = "GB", + continent = "", + defaultCurrency = OnrampCurrencyDTO( + name = "British Pound Sterling", + code = "GBP", + image = "https://hatscripts.github.io/circle-flags/flags/gb.svg", + precision = 2, + ), + onrampAvailable = true, + ), + OnrampCountryDTO( + name = "CANADA", + code = "CA", + image = "https://hatscripts.github.io/circle-flags/flags/ca.svg", + alpha3 = "CA", + continent = "", + defaultCurrency = OnrampCurrencyDTO( + name = "Canadian Dollar", + code = "CAD", + image = "https://hatscripts.github.io/circle-flags/flags/ca.svg", + precision = 2, + ), + onrampAvailable = true, + ), + OnrampCountryDTO( + name = "Hon Kong", + code = "HK", + image = "https://hatscripts.github.io/circle-flags/flags/hk.svg", + alpha3 = "HK", + continent = "", + defaultCurrency = OnrampCurrencyDTO( + name = "Hon Kong Dollar", + code = "HKD", + image = "https://hatscripts.github.io/circle-flags/flags/hk.svg", + precision = 2, + ), + onrampAvailable = true, + ), + OnrampCountryDTO( + name = "Australia", + code = "AU", + image = "https://hatscripts.github.io/circle-flags/flags/au.svg", + alpha3 = "AU", + continent = "", + defaultCurrency = OnrampCurrencyDTO( + name = "Australian Dollar", + code = "AUD", + image = "https://hatscripts.github.io/circle-flags/flags/au.svg", + precision = 2, + ), + onrampAvailable = true, + ), + ) + } +} \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/di/NetworkModule.kt b/core/datasource/src/main/java/com/tangem/datasource/di/NetworkModule.kt index 8b38ca983a..720e14ac0b 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/di/NetworkModule.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/di/NetworkModule.kt @@ -11,6 +11,7 @@ import com.tangem.datasource.api.common.config.managers.ProdApiConfigsManager import com.tangem.datasource.api.common.response.ApiResponseCallAdapterFactory import com.tangem.datasource.api.express.TangemExpressApi import com.tangem.datasource.api.markets.TangemTechMarketsApi +import com.tangem.datasource.api.onramp.MockedOnrampApi import com.tangem.datasource.api.onramp.OnrampApi import com.tangem.datasource.api.stakekit.StakeKitApi import com.tangem.datasource.api.tangemTech.TangemTechApi @@ -98,22 +99,24 @@ internal object NetworkModule { @Provides @Singleton fun provideOnrampApi( - @NetworkMoshi moshi: Moshi, - @ApplicationContext context: Context, - apiConfigsManager: ApiConfigsManager, - appLogsStore: AppLogsStore, + // @NetworkMoshi moshi: Moshi, + // @ApplicationContext context: Context, + // apiConfigsManager: ApiConfigsManager, + // appLogsStore: AppLogsStore, ): OnrampApi { - return createApi( - id = ApiConfig.ID.Express, - moshi = moshi, - context = context, - apiConfigsManager = apiConfigsManager, - clientBuilder = { - addInterceptor( - NetworkLogsSaveInterceptor(appLogsStore), - ) - }, - ) + // TODO: Remove when backend will be ready - [REDACTED_TASK_KEY] + return MockedOnrampApi() + // return createApi( + // id = ApiConfig.ID.Express, + // moshi = moshi, + // context = context, + // apiConfigsManager = apiConfigsManager, + // clientBuilder = { + // addInterceptor( + // NetworkLogsSaveInterceptor(appLogsStore), + // ) + // }, + // ) } @Provides From 095587921d505a5e4565efecb4b0a9a3959a542a Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 17 Oct 2024 12:33:12 +0300 Subject: [PATCH 007/106] Updated on 2026-08-14 --- .../TangemBottomSheetScaffold.kt | 81 +++++++++------- .../com/tangem/core/ui/extensions/Shadow.kt | 95 +++++++++++++++++++ .../markets/portfolio/impl/ui/MyPortfolio.kt | 26 ++++- .../presentation/wallet/ui/WalletScreen.kt | 1 - 4 files changed, 165 insertions(+), 38 deletions(-) create mode 100644 core/ui/src/main/java/com/tangem/core/ui/extensions/Shadow.kt diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/sheetscaffold/TangemBottomSheetScaffold.kt b/core/ui/src/main/java/com/tangem/core/ui/components/sheetscaffold/TangemBottomSheetScaffold.kt index aa68f71190..ccab322580 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/sheetscaffold/TangemBottomSheetScaffold.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/sheetscaffold/TangemBottomSheetScaffold.kt @@ -2,30 +2,57 @@ package com.tangem.core.ui.components.sheetscaffold +import android.graphics.Bitmap +import android.graphics.BlurMaskFilter +import android.renderscript.Allocation +import android.renderscript.Element +import android.renderscript.RenderScript +import android.renderscript.ScriptIntrinsicBlur import androidx.compose.foundation.ExperimentalFoundationApi +import androidx.compose.foundation.background import androidx.compose.foundation.gestures.DraggableAnchors import androidx.compose.foundation.gestures.Orientation import androidx.compose.foundation.gestures.anchoredDraggable +import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.foundation.layout.* +import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.SnackbarHost import androidx.compose.material3.SnackbarHostState import androidx.compose.material3.Surface import androidx.compose.material3.contentColorFor import androidx.compose.runtime.* import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.Shape +import androidx.compose.ui.composed +import androidx.compose.ui.draw.clip +import androidx.compose.ui.draw.drawBehind +import androidx.compose.ui.draw.drawWithCache +import androidx.compose.ui.draw.shadow +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.geometry.Size +import androidx.compose.ui.geometry.center +import androidx.compose.ui.graphics.* +import androidx.compose.ui.graphics.drawscope.DrawScope +import androidx.compose.ui.graphics.drawscope.clipPath +import androidx.compose.ui.graphics.drawscope.drawIntoCanvas import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.layout.Layout +import androidx.compose.ui.layout.onPlaced +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalDensity -import androidx.compose.ui.unit.Dp -import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.* import androidx.compose.ui.util.fastForEach import androidx.compose.ui.util.fastMap import androidx.compose.ui.util.fastMaxOfOrNull +import androidx.compose.ui.zIndex +import androidx.core.graphics.withSave +import androidx.core.graphics.withTranslation import com.tangem.core.ui.components.sheetscaffold.TangemSheetValue.* +import com.tangem.core.ui.extensions.softLayerShadow import com.tangem.core.ui.res.TangemTheme +import com.tangem.core.ui.utils.toPx import kotlinx.coroutines.launch +import kotlin.math.abs +import kotlin.math.pow import kotlin.math.roundToInt /** @@ -51,13 +78,6 @@ import kotlin.math.roundToInt * [Dp.Unspecified] for a sheet that spans the entire screen width. * @param sheetShape the shape of the bottom sheet * @param sheetContainerColor the background color of the bottom sheet - * @param sheetContentColor the preferred content color provided by the bottom sheet to its - * children. Defaults to the matching content color for [sheetContainerColor], or if that is not a - * color from the theme, this will keep the same content color set above the bottom sheet. - * @param sheetTonalElevation when [sheetContainerColor] is [ColorScheme.surface], a translucent - * primary color overlay is applied on top of the container. A higher tonal elevation value will - * result in a darker color in light theme and lighter color in dark theme. See also: [Surface]. - * @param sheetShadowElevation the shadow elevation of the bottom sheet * @param sheetSwipeEnabled whether the sheet swiping is enabled and should react to the user's * input * @param topBar top app bar of the screen, typically a [SmallTopAppBar] @@ -81,10 +101,7 @@ fun TangemBottomSheetScaffold( sheetPeekHeight: Dp, sheetMaxWidth: Dp = 640.dp, sheetShape: Shape = TangemTheme.shapes.bottomSheetLarge, - sheetContainerColor: Color = Color.White, // FIXME - sheetContentColor: Color = contentColorFor(sheetContainerColor), - sheetTonalElevation: Dp = 0.dp, - sheetShadowElevation: Dp = 1.dp, + sheetContainerColor: Color = Color.White, sheetSwipeEnabled: Boolean = true, topBar: @Composable (() -> Unit)? = null, snackbarHost: @Composable (SnackbarHostState) -> Unit = { SnackbarHost(it) }, @@ -109,9 +126,6 @@ fun TangemBottomSheetScaffold( sheetSwipeEnabled = sheetSwipeEnabled, shape = sheetShape, containerColor = sheetContainerColor, - contentColor = sheetContentColor, - tonalElevation = sheetTonalElevation, - shadowElevation = sheetShadowElevation, content = sheetContent, ) }, @@ -178,9 +192,6 @@ private fun StandardBottomSheet( sheetSwipeEnabled: Boolean, shape: Shape, containerColor: Color, - contentColor: Color, - tonalElevation: Dp, - shadowElevation: Dp, content: @Composable ColumnScope.() -> Unit, ) { val scope = rememberCoroutineScope() @@ -202,7 +213,7 @@ private fun StandardBottomSheet( Modifier } - Surface( + Column( modifier = Modifier .widthIn(max = sheetMaxWidth) .fillMaxWidth() @@ -251,16 +262,20 @@ private fun StandardBottomSheet( state = state.anchoredDraggableState, orientation = orientation, enabled = sheetSwipeEnabled, - ), - shape = shape, - color = containerColor, - contentColor = contentColor, - tonalElevation = tonalElevation, - shadowElevation = shadowElevation, + ) + .softLayerShadow( + radius = 8.dp, + color = Color.Black.copy( + alpha = if (isSystemInDarkTheme()) .16f else .08f + ), + shape = shape, + offset = DpOffset(x = 0.dp, y = (-4).dp), + isAlphaContentClip = true + ) + .background(containerColor, shape) + .clip(shape), ) { - Column(Modifier.fillMaxWidth()) { - content() - } + content() } } @@ -293,7 +308,7 @@ private fun BottomSheetScaffoldLayout( ), ) { (topBarMeasurables, bodyMeasurables, bottomSheetMeasurables, snackbarHostMeasurables), - constraints, + constraints, -> val layoutWidth = constraints.maxWidth val layoutHeight = constraints.maxHeight @@ -321,7 +336,7 @@ private fun BottomSheetScaffoldLayout( PartiallyExpanded -> sheetOffset().roundToInt() - snackbarHeight Expanded, Hidden, - -> layoutHeight - snackbarHeight + -> layoutHeight - snackbarHeight } // Placement order is important for elevation diff --git a/core/ui/src/main/java/com/tangem/core/ui/extensions/Shadow.kt b/core/ui/src/main/java/com/tangem/core/ui/extensions/Shadow.kt new file mode 100644 index 0000000000..bc1ce31389 --- /dev/null +++ b/core/ui/src/main/java/com/tangem/core/ui/extensions/Shadow.kt @@ -0,0 +1,95 @@ +package com.tangem.core.ui.extensions + +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.drawWithCache +import androidx.compose.ui.graphics.* +import androidx.compose.ui.graphics.drawscope.DrawScope +import androidx.compose.ui.graphics.drawscope.clipPath +import androidx.compose.ui.graphics.drawscope.drawIntoCanvas +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.DpOffset +import androidx.compose.ui.unit.LayoutDirection +import androidx.compose.ui.unit.dp + +fun Modifier.softLayerShadow( + radius: Dp = 8.dp, + color: Color = Color.Black.copy(alpha = .23f), + shape: Shape = RectangleShape, + spread: Dp = 0.dp, + offset: DpOffset = DpOffset(x = 0.dp, y = 2.dp), + isAlphaContentClip: Boolean = false, +): Modifier = this.drawWithCache { + val radiusPx = radius.toPx() + require(radiusPx > 0.0F) + val paint = Paint().apply { + this.color = color + + asFrameworkPaint().apply { + isDither = true + isAntiAlias = true + + setShadowLayer( + radiusPx, + offset.x.toPx(), + offset.y.toPx(), + color.toArgb(), + ) + } + } + val shapeOutline = shape.createOutline( + size = size, + layoutDirection = LayoutDirection.Rtl, + density = this, + ) + val shapePath = Path().apply { + addOutline(outline = shapeOutline) + } + + val drawShadowBlock: DrawScope.() -> Unit = { + drawIntoCanvas { canvas -> + canvas.withSave { + if (spread.value != 0.0F) { + canvas.scale( + sx = spreadScale( + spread = spread.toPx(), + size = size.width, + ), + sy = spreadScale( + spread = spread.toPx(), + size = size.height, + ), + pivotX = center.x, + pivotY = center.y, + ) + } + + canvas.drawOutline( + outline = shapeOutline, + paint = paint, + ) + } + } + } + + onDrawBehind { + if (isAlphaContentClip) { + clipShadowByPath( + path = shapePath, + block = drawShadowBlock, + ) + } else { + drawShadowBlock() + } + } +} + +@Suppress("UnnecessaryParentheses") +private fun spreadScale(spread: Float, size: Float): Float = 1.0F + ((spread / size) * 2.0F) + +private fun DrawScope.clipShadowByPath(path: Path, block: DrawScope.() -> Unit) { + clipPath( + path = path, + clipOp = ClipOp.Difference, + block = block, + ) +} \ No newline at end of file diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/ui/MyPortfolio.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/ui/MyPortfolio.kt index daaa385754..abd9b2ccdc 100644 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/ui/MyPortfolio.kt +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/ui/MyPortfolio.kt @@ -8,12 +8,14 @@ import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.key +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.util.fastForEachIndexed import com.tangem.core.ui.components.PrimaryButton +import com.tangem.core.ui.components.RectangleShimmer import com.tangem.core.ui.components.SmallButtonShimmer import com.tangem.core.ui.components.TextShimmer import com.tangem.core.ui.components.block.information.InformationBlock @@ -76,10 +78,26 @@ private fun Title() { private fun AddButton(state: AddButtonState, onClick: () -> Unit) { when (state) { AddButtonState.Loading -> { - SmallButtonShimmer( - modifier = Modifier.size(width = TangemTheme.dimens.size63, height = TangemTheme.dimens.size18), - shape = RoundedCornerShape(TangemTheme.dimens.radius3), - ) + Box { + SmallButtonShimmer( + modifier = Modifier.width(width = TangemTheme.dimens.size63), + shape = RoundedCornerShape(TangemTheme.dimens.radius3), + withIcon = true, + ) + + Box( + Modifier + .matchParentSize() + .background(TangemTheme.colors.background.action), + ) + + RectangleShimmer( + modifier = Modifier + .align(Alignment.Center) + .size(width = TangemTheme.dimens.size63, height = TangemTheme.dimens.size18), + radius = TangemTheme.dimens.radius3, + ) + } } AddButtonState.Available, AddButtonState.Unavailable, diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletScreen.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletScreen.kt index f9f733cd80..1d289a12df 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletScreen.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletScreen.kt @@ -391,7 +391,6 @@ private inline fun BaseScaffoldWithMarkets( sheetContainerColor = backgroundColor.value, scaffoldState = scaffoldState, sheetPeekHeight = peekHeight, - sheetShadowElevation = 8.dp, sheetShape = TangemTheme.shapes.bottomSheetLarge, sheetContent = { // hide bottom sheet when back pressed From 3183f5b955e9930253cb14093b78859d29cedb9d Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 17 Oct 2024 19:44:53 +0500 Subject: [PATCH 008/106] Updated on 2026-08-14 --- app/build.gradle.kts | 2 + .../com/tangem/tap/ApplicationEntryPoint.kt | 3 ++ .../java/com/tangem/tap/TangemApplication.kt | 5 +++ .../tap/proxy/redux/DaggerGraphState.kt | 2 + .../configs/feature_toggles_config.json | 4 ++ features/onramp/api/.gitignore | 1 + features/onramp/api/build.gradle.kts | 16 ++++++++ .../features/onramp/OnrampFeatureToggles.kt | 6 +++ .../onramp/component/OnrampComponent.kt | 9 +++++ features/onramp/impl/.gitignore | 1 + features/onramp/impl/build.gradle.kts | 38 +++++++++++++++++++ .../onramp/DefaultOnrampFeatureToggles.kt | 11 ++++++ .../features/onramp/di/FeatureModule.kt | 21 ++++++++++ settings.gradle.kts | 3 ++ 14 files changed, 122 insertions(+) create mode 100644 features/onramp/api/.gitignore create mode 100644 features/onramp/api/build.gradle.kts create mode 100644 features/onramp/api/src/main/kotlin/com/tangem/features/onramp/OnrampFeatureToggles.kt create mode 100644 features/onramp/api/src/main/kotlin/com/tangem/features/onramp/component/OnrampComponent.kt create mode 100644 features/onramp/impl/.gitignore create mode 100644 features/onramp/impl/build.gradle.kts create mode 100644 features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/DefaultOnrampFeatureToggles.kt create mode 100644 features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/di/FeatureModule.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 0412c6eacf..d9d1aaef00 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -143,6 +143,8 @@ dependencies { implementation(projects.features.walletSettings.impl) implementation(projects.features.markets.api) implementation(projects.features.markets.impl) + implementation(projects.features.onramp.api) + implementation(projects.features.onramp.impl) /** AndroidX libraries */ implementation(deps.androidx.core.ktx) diff --git a/app/src/main/java/com/tangem/tap/ApplicationEntryPoint.kt b/app/src/main/java/com/tangem/tap/ApplicationEntryPoint.kt index 7d37eacaf5..a425711dd5 100644 --- a/app/src/main/java/com/tangem/tap/ApplicationEntryPoint.kt +++ b/app/src/main/java/com/tangem/tap/ApplicationEntryPoint.kt @@ -31,6 +31,7 @@ import com.tangem.domain.walletmanager.WalletManagersFacade import com.tangem.domain.wallets.legacy.UserWalletsListManager import com.tangem.domain.wallets.repository.WalletsRepository import com.tangem.domain.wallets.usecase.GenerateWalletNameUseCase +import com.tangem.features.onramp.OnrampFeatureToggles import com.tangem.tap.common.log.TangemAppLoggerInitializer import com.tangem.tap.domain.scanCard.CardScanningFeatureToggles import com.tangem.tap.domain.walletconnect2.domain.WalletConnectSessionsRepository @@ -119,4 +120,6 @@ interface ApplicationEntryPoint { fun getHomeFeatureToggles(): HomeFeatureToggles fun getGetUserCountryCodeUseCase(): GetUserCountryUseCase + + fun getOnrampFeatureToggles(): OnrampFeatureToggles } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/TangemApplication.kt b/app/src/main/java/com/tangem/tap/TangemApplication.kt index 0b5a37f14d..530337b2cd 100644 --- a/app/src/main/java/com/tangem/tap/TangemApplication.kt +++ b/app/src/main/java/com/tangem/tap/TangemApplication.kt @@ -45,6 +45,7 @@ import com.tangem.domain.walletmanager.WalletManagersFacade import com.tangem.domain.wallets.legacy.UserWalletsListManager import com.tangem.domain.wallets.repository.WalletsRepository import com.tangem.domain.wallets.usecase.GenerateWalletNameUseCase +import com.tangem.features.onramp.OnrampFeatureToggles import com.tangem.tap.common.analytics.AnalyticsFactory import com.tangem.tap.common.analytics.api.AnalyticsHandlerBuilder import com.tangem.tap.common.analytics.handlers.amplitude.AmplitudeAnalyticsHandler @@ -189,6 +190,9 @@ abstract class TangemApplication : Application(), ImageLoaderFactory { private val getUserCountryUseCase: GetUserCountryUseCase get() = entryPoint.getGetUserCountryCodeUseCase() + + private val onrampFeatureToggles: OnrampFeatureToggles + get() = entryPoint.getOnrampFeatureToggles() // endregion override fun onCreate() { @@ -272,6 +276,7 @@ abstract class TangemApplication : Application(), ImageLoaderFactory { transactionSignerFactory = transactionSignerFactory, homeFeatureToggles = homeFeatureToggles, getUserCountryUseCase = getUserCountryUseCase, + onrampFeatureToggles = onrampFeatureToggles, ), ), ) diff --git a/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphState.kt b/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphState.kt index 56c13b9a41..8d6b257f74 100644 --- a/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphState.kt +++ b/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphState.kt @@ -30,6 +30,7 @@ import com.tangem.domain.wallets.legacy.UserWalletsListManager import com.tangem.domain.wallets.repository.WalletsRepository import com.tangem.domain.wallets.usecase.GenerateWalletNameUseCase import com.tangem.feature.qrscanning.QrScanningRouter +import com.tangem.features.onramp.OnrampFeatureToggles import com.tangem.features.pushnotifications.api.navigation.PushNotificationsRouter import com.tangem.features.send.api.navigation.SendRouter import com.tangem.features.staking.api.navigation.StakingRouter @@ -87,4 +88,5 @@ data class DaggerGraphState( val transactionSignerFactory: TransactionSignerFactory? = null, val homeFeatureToggles: HomeFeatureToggles? = null, val getUserCountryUseCase: GetUserCountryUseCase? = null, + val onrampFeatureToggles: OnrampFeatureToggles? = null, ) : StateType \ No newline at end of file diff --git a/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json b/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json index 0aa128c712..97119cabb4 100644 --- a/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json +++ b/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json @@ -22,5 +22,9 @@ { "name": "MIGRATE_USER_COUNTRY_CODE_ENABLED", "version": "5.17.0" + }, + { + "name": "ONRAMP_ENABLED", + "version": "undefined" } ] diff --git a/features/onramp/api/.gitignore b/features/onramp/api/.gitignore new file mode 100644 index 0000000000..796b96d1c4 --- /dev/null +++ b/features/onramp/api/.gitignore @@ -0,0 +1 @@ +/build diff --git a/features/onramp/api/build.gradle.kts b/features/onramp/api/build.gradle.kts new file mode 100644 index 0000000000..dfd87a2e88 --- /dev/null +++ b/features/onramp/api/build.gradle.kts @@ -0,0 +1,16 @@ +plugins { + alias(deps.plugins.android.library) + alias(deps.plugins.kotlin.android) + id("kotlin-parcelize") + id("configuration") +} + +android { + namespace = "com.tangem.features.onramp.api" +} + +dependencies { + /* Project - Core */ + implementation(projects.core.decompose) + implementation(projects.core.ui) +} \ No newline at end of file diff --git a/features/onramp/api/src/main/kotlin/com/tangem/features/onramp/OnrampFeatureToggles.kt b/features/onramp/api/src/main/kotlin/com/tangem/features/onramp/OnrampFeatureToggles.kt new file mode 100644 index 0000000000..7d46cb6ad1 --- /dev/null +++ b/features/onramp/api/src/main/kotlin/com/tangem/features/onramp/OnrampFeatureToggles.kt @@ -0,0 +1,6 @@ +package com.tangem.features.onramp + +interface OnrampFeatureToggles { + + val isFeatureEnabled: Boolean +} \ No newline at end of file diff --git a/features/onramp/api/src/main/kotlin/com/tangem/features/onramp/component/OnrampComponent.kt b/features/onramp/api/src/main/kotlin/com/tangem/features/onramp/component/OnrampComponent.kt new file mode 100644 index 0000000000..b5cd6141fb --- /dev/null +++ b/features/onramp/api/src/main/kotlin/com/tangem/features/onramp/component/OnrampComponent.kt @@ -0,0 +1,9 @@ +package com.tangem.features.onramp.component + +import com.tangem.core.decompose.factory.ComponentFactory +import com.tangem.core.ui.decompose.ComposableContentComponent + +interface OnrampComponent : ComposableContentComponent { + + interface Factory : ComponentFactory +} \ No newline at end of file diff --git a/features/onramp/impl/.gitignore b/features/onramp/impl/.gitignore new file mode 100644 index 0000000000..796b96d1c4 --- /dev/null +++ b/features/onramp/impl/.gitignore @@ -0,0 +1 @@ +/build diff --git a/features/onramp/impl/build.gradle.kts b/features/onramp/impl/build.gradle.kts new file mode 100644 index 0000000000..37ef2d093d --- /dev/null +++ b/features/onramp/impl/build.gradle.kts @@ -0,0 +1,38 @@ +plugins { + alias(deps.plugins.android.library) + alias(deps.plugins.kotlin.android) + alias(deps.plugins.kotlin.kapt) + alias(deps.plugins.hilt.android) + id("configuration") +} + +android { + namespace = "com.tangem.features.onramp.impl" +} + +dependencies { + /** Project - API */ + implementation(projects.features.onramp.api) + + /** Project - Core */ + implementation(projects.core.decompose) + implementation(projects.core.ui) + implementation(projects.core.featuretoggles) + + /** DI */ + implementation(deps.hilt.android) + kapt(deps.hilt.kapt) + + /* AndroidX */ + implementation(deps.androidx.activity.compose) + implementation(deps.lifecycle.compose) + + /* Compose */ + implementation(deps.compose.ui) + implementation(deps.compose.ui.tooling) + implementation(deps.compose.accompanist.systemUiController) + implementation(deps.compose.foundation) + implementation(deps.compose.material3) + implementation(deps.compose.shimmer) + implementation(deps.compose.coil) +} \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/DefaultOnrampFeatureToggles.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/DefaultOnrampFeatureToggles.kt new file mode 100644 index 0000000000..15d7dd6713 --- /dev/null +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/DefaultOnrampFeatureToggles.kt @@ -0,0 +1,11 @@ +package com.tangem.features.onramp + +import com.tangem.core.featuretoggle.manager.FeatureTogglesManager + +internal class DefaultOnrampFeatureToggles( + private val featureTogglesManager: FeatureTogglesManager, +) : OnrampFeatureToggles { + + override val isFeatureEnabled: Boolean + get() = featureTogglesManager.isFeatureEnabled("ONRAMP_ENABLED") +} \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/di/FeatureModule.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/di/FeatureModule.kt new file mode 100644 index 0000000000..2c39321176 --- /dev/null +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/di/FeatureModule.kt @@ -0,0 +1,21 @@ +package com.tangem.features.onramp.di + +import com.tangem.core.featuretoggle.manager.FeatureTogglesManager +import com.tangem.features.onramp.DefaultOnrampFeatureToggles +import com.tangem.features.onramp.OnrampFeatureToggles +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +internal object FeatureModule { + + @Provides + @Singleton + fun provideFeatureToggles(featureTogglesManager: FeatureTogglesManager): OnrampFeatureToggles { + return DefaultOnrampFeatureToggles(featureTogglesManager) + } +} \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index e8e6fe3fcf..f3b70b2d8b 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -198,6 +198,9 @@ include(":features:wallet-settings:impl") include(":features:markets:api") include(":features:markets:impl") + +include(":features:onramp:api") +include(":features:onramp:impl") // endregion Feature modules // region Domain modules From 7ca9946f476d616b430e12cd54106e6557356175 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 18 Oct 2024 12:20:15 +0300 Subject: [PATCH 009/106] Updated on 2026-08-14 --- .../converters/AmountStateConverter.kt | 7 +- .../common/ui/amountScreen/ui/AmountBlock.kt | 11 +- .../common/ui/amountScreen/ui/AmountField.kt | 8 +- .../common/ui/notifications/NotificationUM.kt | 16 +- .../ui/notifications/NotificationsFactory.kt | 26 +-- .../ui/tokens/TokenItemStateConverter.kt | 10 +- .../inputrow/InputRowImageSelector.kt | 5 +- .../bigdecimal/BigDecimalCryptoFormat.kt | 33 +++- .../bigdecimal/BigDecimalSimpleFormat.kt | 34 ++++ .../core/ui/utils/BigDecimalFormatter.kt | 159 ------------------ .../bigdecimal/BigDecimalCryptoFormatTest.kt | 34 ++++ .../utils/CryptoCurrencyFormatExtensions.kt | 43 ----- .../usecase/SendTransactionUseCase.kt | 5 +- .../impl/model/MarketsTokenDetailsModel.kt | 16 +- .../impl/model/formatter/Formatters.kt | 9 +- .../block/impl/model/TokenMarketBlockModel.kt | 7 +- .../converters/MarketsTokenItemConverter.kt | 6 +- .../SendRecipientHistoryListConverter.kt | 8 +- .../presentation/ui/SendNavigationButtons.kt | 17 +- .../ui/fee/SendSpeedSelectorItem.kt | 15 +- .../impl/presentation/ui/send/FeeBlock.kt | 15 +- .../state/converters/BalanceItemConverter.kt | 7 +- .../RewardsValidatorStateConverter.kt | 9 +- .../converters/YieldBalancesConverter.kt | 7 +- .../SetInitialDataStateTransformer.kt | 26 +-- .../AmountRequirementStateTransformer.kt | 11 +- .../ShowApprovalBottomSheetTransformer.kt | 10 +- .../ui/StakingClaimRewardsValidatorContent.kt | 8 +- .../ui/StakingInitialInfoContent.kt | 8 +- .../ui/StakingValidatorListContent.kt | 5 +- .../presentation/ui/block/StakingFeeBlock.kt | 15 +- .../presentation/ui/block/ValidatorBlock.kt | 5 +- .../feature/swap/domain/models/SwapAmount.kt | 5 - .../swap/domain/models/ui/AmountFormatter.kt | 11 +- .../feature/swap/domain/SwapInteractorImpl.kt | 2 +- .../swap/converters/TokensDataConverter.kt | 10 +- .../feature/swap/di/SwapSingletonModule.kt | 18 ++ .../swap/di/impl/DefaultAmountFormatter.kt | 18 ++ .../tangem/feature/swap/ui/StateBuilder.kt | 24 ++- .../tangem/feature/swap/utils/SwapUtils.kt | 7 + .../feature/swap/viewmodels/SwapViewModel.kt | 2 +- ...TokenDetailsBalanceSelectStateConverter.kt | 4 +- .../TokenDetailsLoadedBalanceConverter.kt | 17 +- .../TokenDetailsNotificationConverter.kt | 24 +-- ...enDetailsSwapTransactionsStateConverter.kt | 12 +- ...tailsTxHistoryTransactionStateConverter.kt | 5 +- .../domain/WalletAdditionalInfoFactory.kt | 11 +- .../SetBalancesAndLimitsTransformer.kt | 18 +- .../BalancesAndLimitsBottomSheetConverter.kt | 9 +- .../SingleWalletMarketPriceConverter.kt | 4 +- .../converter/TxHistoryItemStateConverter.kt | 5 +- .../VisaTxDetailsBottomSheetConverter.kt | 8 +- .../VisaTxHistoryItemStateConverter.kt | 8 +- 53 files changed, 355 insertions(+), 462 deletions(-) create mode 100644 core/ui/src/main/java/com/tangem/core/ui/format/bigdecimal/BigDecimalSimpleFormat.kt delete mode 100644 core/utils/src/main/java/com/tangem/utils/CryptoCurrencyFormatExtensions.kt create mode 100644 features/swap/presentation/src/main/java/com/tangem/feature/swap/di/SwapSingletonModule.kt create mode 100644 features/swap/presentation/src/main/java/com/tangem/feature/swap/di/impl/DefaultAmountFormatter.kt diff --git a/common/ui/src/main/java/com/tangem/common/ui/amountScreen/converters/AmountStateConverter.kt b/common/ui/src/main/java/com/tangem/common/ui/amountScreen/converters/AmountStateConverter.kt index 410a23b468..3445c4859f 100644 --- a/common/ui/src/main/java/com/tangem/common/ui/amountScreen/converters/AmountStateConverter.kt +++ b/common/ui/src/main/java/com/tangem/common/ui/amountScreen/converters/AmountStateConverter.kt @@ -3,13 +3,14 @@ package com.tangem.common.ui.amountScreen.converters import com.tangem.common.ui.R import com.tangem.common.ui.amountScreen.AmountScreenClickIntents import com.tangem.common.ui.amountScreen.converters.field.AmountFieldConverter -import com.tangem.common.ui.amountScreen.models.AmountState import com.tangem.common.ui.amountScreen.models.AmountSegmentedButtonsConfig +import com.tangem.common.ui.amountScreen.models.AmountState import com.tangem.core.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 -import com.tangem.core.ui.utils.BigDecimalFormatter.formatCryptoAmount +import com.tangem.core.ui.format.bigdecimal.crypto +import com.tangem.core.ui.format.bigdecimal.format import com.tangem.core.ui.utils.BigDecimalFormatter.formatFiatAmount import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.tokens.model.CryptoCurrencyStatus @@ -49,7 +50,7 @@ class AmountStateConverter( val appCurrency = appCurrencyProvider() val status = cryptoCurrencyStatusProvider() val fiat = formatFiatAmount(status.value.fiatAmount, appCurrency.code, appCurrency.symbol) - val crypto = formatCryptoAmount(status.value.amount, status.currency.symbol, status.currency.decimals) + val crypto = status.value.amount.format { crypto(status.currency) } val noFeeRate = status.value.fiatRate.isNullOrZero() return AmountState.Data( diff --git a/common/ui/src/main/java/com/tangem/common/ui/amountScreen/ui/AmountBlock.kt b/common/ui/src/main/java/com/tangem/common/ui/amountScreen/ui/AmountBlock.kt index b2903b20cc..bcac8bb21b 100644 --- a/common/ui/src/main/java/com/tangem/common/ui/amountScreen/ui/AmountBlock.kt +++ b/common/ui/src/main/java/com/tangem/common/ui/amountScreen/ui/AmountBlock.kt @@ -19,16 +19,20 @@ import com.tangem.common.ui.amountScreen.models.AmountState import com.tangem.common.ui.amountScreen.preview.AmountStatePreviewData import com.tangem.core.ui.components.ResizableText import com.tangem.core.ui.components.currency.icon.CurrencyIcon -import com.tangem.core.ui.res.TangemThemePreview +import com.tangem.core.ui.format.bigdecimal.anyDecimals +import com.tangem.core.ui.format.bigdecimal.crypto +import com.tangem.core.ui.format.bigdecimal.format import com.tangem.core.ui.res.TangemTheme +import com.tangem.core.ui.res.TangemThemePreview import com.tangem.core.ui.utils.BigDecimalFormatter +import java.math.BigDecimal @Composable fun AmountBlock(amountState: AmountState, isClickDisabled: Boolean, isEditingDisabled: Boolean, onClick: () -> Unit) { if (amountState !is AmountState.Data) return val amount = amountState.amountTextField - val cryptoAmount = BigDecimalFormatter.formatWithSymbol(amount.value, amount.cryptoAmount.currencySymbol) + val cryptoAmount = formatWithSymbol(amount.value, amount.cryptoAmount.currencySymbol) val fiatAmount = BigDecimalFormatter.formatFiatAmount( fiatAmount = amount.fiatAmount.value, fiatCurrencySymbol = amount.fiatAmount.currencySymbol, @@ -77,6 +81,9 @@ fun AmountBlock(amountState: AmountState, isClickDisabled: Boolean, isEditingDis } } +fun formatWithSymbol(amount: String, symbol: String) = + BigDecimal.ZERO.format { crypto(symbol, 0).anyDecimals() }.replace("0", amount) + // region Preview @Preview @Preview(uiMode = Configuration.UI_MODE_NIGHT_YES) diff --git a/common/ui/src/main/java/com/tangem/common/ui/amountScreen/ui/AmountField.kt b/common/ui/src/main/java/com/tangem/common/ui/amountScreen/ui/AmountField.kt index d8347d67af..c74f183aa6 100644 --- a/common/ui/src/main/java/com/tangem/common/ui/amountScreen/ui/AmountField.kt +++ b/common/ui/src/main/java/com/tangem/common/ui/amountScreen/ui/AmountField.kt @@ -24,6 +24,8 @@ import com.tangem.core.ui.components.fields.AmountTextField import com.tangem.core.ui.components.fields.visualtransformations.AmountVisualTransformation import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.extensions.resolveReference +import com.tangem.core.ui.format.bigdecimal.crypto +import com.tangem.core.ui.format.bigdecimal.format import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.utils.BigDecimalFormatter import com.tangem.core.ui.utils.rememberDecimalFormat @@ -92,11 +94,7 @@ private fun AmountSecondary(amountField: AmountFieldModel, appCurrencyCode: Stri ), ) { val text = if (amountField.isFiatValue) { - BigDecimalFormatter.formatCryptoAmount( - cryptoAmount = secondaryAmount.value, - cryptoCurrency = secondaryAmount.currencySymbol, - decimals = secondaryAmount.decimals, - ) + secondaryAmount.value.format { crypto(secondaryAmount.currencySymbol, secondaryAmount.decimals) } } else { BigDecimalFormatter.formatFiatAmount( fiatAmount = secondaryAmount.value, diff --git a/common/ui/src/main/java/com/tangem/common/ui/notifications/NotificationUM.kt b/common/ui/src/main/java/com/tangem/common/ui/notifications/NotificationUM.kt index accc615136..8ca24539b5 100644 --- a/common/ui/src/main/java/com/tangem/common/ui/notifications/NotificationUM.kt +++ b/common/ui/src/main/java/com/tangem/common/ui/notifications/NotificationUM.kt @@ -6,7 +6,9 @@ import com.tangem.core.ui.components.notifications.NotificationConfig import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.extensions.resourceReference import com.tangem.core.ui.extensions.wrappedList -import com.tangem.core.ui.utils.BigDecimalFormatter +import com.tangem.core.ui.format.bigdecimal.crypto +import com.tangem.core.ui.format.bigdecimal.format +import com.tangem.core.ui.format.bigdecimal.shorted import java.math.BigDecimal sealed class NotificationUM(val config: NotificationConfig) { @@ -267,8 +269,8 @@ sealed class NotificationUM(val config: NotificationConfig) { subtitle = resourceReference( R.string.koinos_insufficient_mana_to_send_koin_description, formatArgs = wrappedList( - BigDecimalFormatter.formatCryptoAmountShorted(mana, "", Blockchain.Koinos.decimals()), - BigDecimalFormatter.formatCryptoAmountShorted(maxMana, "", Blockchain.Koinos.decimals()), + mana.format { crypto("", Blockchain.Koinos.decimals()).shorted() }, + maxMana.format { crypto("", Blockchain.Koinos.decimals()).shorted() }, ), ), ) @@ -286,11 +288,9 @@ sealed class NotificationUM(val config: NotificationConfig) { subtitle = resourceReference( R.string.koinos_mana_exceeds_koin_balance_description, formatArgs = wrappedList( - BigDecimalFormatter.formatCryptoAmount( - availableKoinForTransfer, - Blockchain.Koinos.currency, - Blockchain.Koinos.decimals(), - ), + availableKoinForTransfer.format { + crypto(Blockchain.Koinos.currency, Blockchain.Koinos.decimals()) + }, ), ), buttonState = NotificationConfig.ButtonsState.PrimaryButtonConfig( diff --git a/common/ui/src/main/java/com/tangem/common/ui/notifications/NotificationsFactory.kt b/common/ui/src/main/java/com/tangem/common/ui/notifications/NotificationsFactory.kt index 36c15ab3c6..51df42310b 100644 --- a/common/ui/src/main/java/com/tangem/common/ui/notifications/NotificationsFactory.kt +++ b/common/ui/src/main/java/com/tangem/common/ui/notifications/NotificationsFactory.kt @@ -6,7 +6,9 @@ import com.tangem.common.ui.R import com.tangem.common.ui.amountScreen.models.AmountFieldModel import com.tangem.common.ui.amountScreen.utils.getFiatString import com.tangem.core.ui.extensions.networkIconResId -import com.tangem.core.ui.utils.BigDecimalFormatter +import com.tangem.core.ui.format.bigdecimal.crypto +import com.tangem.core.ui.format.bigdecimal.format +import com.tangem.core.ui.format.bigdecimal.uncapped import com.tangem.core.ui.utils.parseBigDecimal import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.tokens.model.CryptoCurrency @@ -65,10 +67,9 @@ object NotificationsFactory { if (!isAccountFunded && reserveAmount != null && reserveAmount > sendingAmount) { add( NotificationUM.Error.ReserveAmount( - BigDecimalFormatter.formatCryptoAmount( - cryptoAmount = sendingAmount, - cryptoCurrency = cryptoCurrency, - ), + sendingAmount.format { + crypto(cryptoCurrency) + }, ), ) } @@ -87,10 +88,7 @@ object NotificationsFactory { NotificationUM.Error.TransactionLimitError( cryptoCurrency = cryptoCurrency.name, utxoLimit = utxoLimit.maxLimit.toPlainString(), - amountLimit = BigDecimalFormatter.formatCryptoAmount( - cryptoAmount = utxoLimit.maxAmount, - cryptoCurrency = cryptoCurrency, - ), + amountLimit = utxoLimit.maxAmount.format { crypto(cryptoCurrency) }, onConfirmClick = { onReduceClick( utxoLimit.maxAmount, @@ -124,10 +122,7 @@ object NotificationsFactory { if (existentialDeposit != null && diff >= BigDecimal.ZERO && existentialDeposit > diff) { add( NotificationUM.Error.ExistentialDeposit( - deposit = BigDecimalFormatter.formatCryptoAmountUncapped( - cryptoAmount = existentialDeposit, - cryptoCurrency = cryptoCurrency, - ), + deposit = existentialDeposit.format { crypto(cryptoCurrency).uncapped() }, onConfirmClick = { onReduceClick( existentialDeposit, @@ -155,10 +150,7 @@ object NotificationsFactory { if (isFeeCoverage) { add( NotificationUM.Warning.FeeCoverageNotification( - cryptoAmount = BigDecimalFormatter.formatCryptoAmountUncapped( - cryptoAmount = cryptoDiff, - cryptoCurrency = cryptoCurrency, - ), + cryptoAmount = cryptoDiff.format { crypto(cryptoCurrency).uncapped() }, fiatAmount = getFiatString( value = cryptoDiff, rate = fiatRate, diff --git a/common/ui/src/main/java/com/tangem/common/ui/tokens/TokenItemStateConverter.kt b/common/ui/src/main/java/com/tangem/common/ui/tokens/TokenItemStateConverter.kt index 964b1a0929..913b07dd84 100644 --- a/common/ui/src/main/java/com/tangem/common/ui/tokens/TokenItemStateConverter.kt +++ b/common/ui/src/main/java/com/tangem/common/ui/tokens/TokenItemStateConverter.kt @@ -4,6 +4,9 @@ import com.tangem.core.ui.components.currency.icon.converter.CryptoCurrencyToIco import com.tangem.core.ui.components.marketprice.PriceChangeType import com.tangem.core.ui.components.marketprice.utils.PriceChangeConverter import com.tangem.core.ui.components.token.state.TokenItemState +import com.tangem.core.ui.format.bigdecimal.crypto +import com.tangem.core.ui.format.bigdecimal.format +import com.tangem.core.ui.format.bigdecimal.percent import com.tangem.core.ui.utils.BigDecimalFormatter import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.staking.model.stakekit.YieldBalance @@ -80,7 +83,7 @@ class TokenItemStateConverter( private fun CryptoCurrencyStatus.getFormattedAmount(): String { val amount = value.amount?.plus(getStakedBalance()) ?: return DASH_SIGN - return BigDecimalFormatter.formatCryptoAmount(amount, currency.symbol, currency.decimals) + return amount.format { crypto(currency) } } private fun CryptoCurrencyStatus.getFormattedFiatAmount(): String { @@ -167,10 +170,7 @@ class TokenItemStateConverter( return if (fiatRate != null && priceChange != null) { TokenItemState.SubtitleState.CryptoPriceContent( price = fiatRate.getFormattedCryptoPrice(appCurrency), - priceChangePercent = BigDecimalFormatter.formatPercent( - percent = priceChange, - useAbsoluteValue = true, - ), + priceChangePercent = priceChange.format { percent() }, type = priceChange.getPriceChangeType(), ) } else { diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/inputrow/InputRowImageSelector.kt b/core/ui/src/main/java/com/tangem/core/ui/components/inputrow/InputRowImageSelector.kt index a9921a83b5..e94c8d5191 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/inputrow/InputRowImageSelector.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/inputrow/InputRowImageSelector.kt @@ -24,9 +24,10 @@ import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.extensions.annotatedReference import com.tangem.core.ui.extensions.combinedReference import com.tangem.core.ui.extensions.resourceReference +import com.tangem.core.ui.format.bigdecimal.format +import com.tangem.core.ui.format.bigdecimal.percent import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemThemePreview -import com.tangem.core.ui.utils.BigDecimalFormatter import java.math.BigDecimal /** @@ -98,7 +99,7 @@ private fun InputRowImageSelectorPreview( append(" ") withStyle(style = SpanStyle(color = TangemTheme.colors.text.accent)) { append( - BigDecimalFormatter.formatPercent(BigDecimal.ZERO, true), + BigDecimal.ZERO.format { percent() }, ) } }, diff --git a/core/ui/src/main/java/com/tangem/core/ui/format/bigdecimal/BigDecimalCryptoFormat.kt b/core/ui/src/main/java/com/tangem/core/ui/format/bigdecimal/BigDecimalCryptoFormat.kt index 6973261be0..eea3130209 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/format/bigdecimal/BigDecimalCryptoFormat.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/format/bigdecimal/BigDecimalCryptoFormat.kt @@ -120,6 +120,26 @@ fun BigDecimalCryptoFormat.uncapped() = BigDecimalFormat { value -> ) } +/** + * Format for displaying crypto amounts with a fixed number of decimals. + */ +fun BigDecimalCryptoFormat.anyDecimals(maxDecimals: Int = decimals, minDecimals: Int = decimals) = + BigDecimalFormat { value -> + val formatter = NumberFormat.getCurrencyInstance(locale).apply { + currency = usdCurrency + maximumFractionDigits = maxDecimals + minimumFractionDigits = minDecimals + isGroupingUsed = true + roundingMode = RoundingMode.HALF_UP + } + + formatter.format(value) + .replaceFiatSymbolWithCrypto( + fiatCurrencySymbol = usdCurrency.getSymbol(locale), + cryptoCurrencySymbol = symbol, + ) + } + /** * Format for displaying fees. * If the fee is less than the threshold, it will be displayed as a fixed value "<0.000001 BTC", " { val withoutSymbol = str.dropLast(fiatCurrencySymbol.length) + + if (cryptoCurrencySymbol.isBlank()) { + return withoutSymbol + } + val last = withoutSymbol.lastOrNull() ?: return cryptoCurrencySymbol append(withoutSymbol) @@ -200,12 +225,16 @@ internal fun String.replaceFiatSymbolWithCrypto( append(CURRENCY_SPACE) } - append(cryptoCurrencySymbol) - val withoutSymbol = str.drop(fiatCurrencySymbol.length) val first = withoutSymbol.firstOrNull() ?: return cryptoCurrencySymbol + if (cryptoCurrencySymbol.isBlank()) { + return withoutSymbol + } + + append(cryptoCurrencySymbol) + if (first.isNotWhitespace()) { append(CURRENCY_SPACE) } diff --git a/core/ui/src/main/java/com/tangem/core/ui/format/bigdecimal/BigDecimalSimpleFormat.kt b/core/ui/src/main/java/com/tangem/core/ui/format/bigdecimal/BigDecimalSimpleFormat.kt new file mode 100644 index 0000000000..f6c5067d65 --- /dev/null +++ b/core/ui/src/main/java/com/tangem/core/ui/format/bigdecimal/BigDecimalSimpleFormat.kt @@ -0,0 +1,34 @@ +package com.tangem.core.ui.format.bigdecimal + +import java.math.BigDecimal +import java.math.RoundingMode +import java.text.NumberFormat +import java.util.Locale + +open class BigDecimalSimpleFormat( + val decimals: Int, + val locale: Locale = Locale.getDefault(), +) : BigDecimalFormat { + + override fun invoke(value: BigDecimal): String = default()(value) +} + +// == Initializers == + +fun BigDecimalFormatScope.simple(decimals: Int, locale: Locale = Locale.getDefault()) = BigDecimalSimpleFormat( + decimals = decimals, + locale = locale, +) + +// == Formatters == + +fun BigDecimalSimpleFormat.default() = BigDecimalFormat { value -> + val formatter = NumberFormat.getInstance(locale).apply { + maximumFractionDigits = decimals + minimumFractionDigits = 0 + isGroupingUsed = true + roundingMode = RoundingMode.HALF_UP + } + + formatter.format(value) +} \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/utils/BigDecimalFormatter.kt b/core/ui/src/main/java/com/tangem/core/ui/utils/BigDecimalFormatter.kt index fd6a353bb1..744a2d9bf3 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/utils/BigDecimalFormatter.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/utils/BigDecimalFormatter.kt @@ -1,6 +1,5 @@ package com.tangem.core.ui.utils -import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.utils.StringsSigns.DASH_SIGN import com.tangem.utils.StringsSigns.LOWER_SIGN import com.tangem.utils.StringsSigns.TILDE_SIGN @@ -16,10 +15,8 @@ object BigDecimalFormatter { const val EMPTY_BALANCE_SIGN = DASH_SIGN private const val CAN_BE_LOWER_SIGN = LOWER_SIGN - private val FORMAT_THRESHOLD = BigDecimal("0.01") private val FIAT_FORMAT_THRESHOLD = BigDecimal("0.01") - private val CRYPTO_FEE_FORMAT_THRESHOLD = BigDecimal("0.000001") private const val FIAT_MARKET_DEFAULT_DIGITS = 2 private const val FIAT_MARKET_EXTENDED_DIGITS = 6 @@ -27,138 +24,6 @@ object BigDecimalFormatter { private val usdCurrency = Currency.getInstance("USD") - @Deprecated("Use BigDecimal.format") - fun formatCryptoAmount( - cryptoAmount: BigDecimal?, - cryptoCurrency: String, - decimals: Int, - locale: Locale = Locale.getDefault(), - ): String { - if (cryptoAmount == null) return EMPTY_BALANCE_SIGN - - val formatter = NumberFormat.getNumberInstance(locale).apply { - maximumFractionDigits = decimals.coerceAtMost(maximumValue = 8) - minimumFractionDigits = 2 - isGroupingUsed = true - roundingMode = RoundingMode.HALF_UP - } - - return formatter.format(cryptoAmount).let { - if (cryptoCurrency.isEmpty()) { - it - } else { - it + "\u2009$cryptoCurrency" - } - } - } - - @Deprecated("Use BigDecimal.format") - fun formatCryptoAmountShorted( - cryptoAmount: BigDecimal?, - cryptoCurrency: String, - decimals: Int, - locale: Locale = Locale.getDefault(), - ): String { - if (cryptoAmount == null) return EMPTY_BALANCE_SIGN - - val formatter = if (cryptoAmount.isMoreThanThreshold()) { - NumberFormat.getNumberInstance(locale).apply { - maximumFractionDigits = 2 - minimumFractionDigits = 2 - isGroupingUsed = true - roundingMode = RoundingMode.HALF_UP - } - } else { - NumberFormat.getNumberInstance(locale).apply { - maximumFractionDigits = decimals.coerceAtMost(maximumValue = 6) - minimumFractionDigits = 2 - isGroupingUsed = true - roundingMode = RoundingMode.DOWN - } - } - - return formatter.format(cryptoAmount).let { - if (cryptoCurrency.isEmpty()) { - it - } else { - it + "\u2009$cryptoCurrency" - } - } - } - - @Deprecated("Use BigDecimal.format") - fun formatCryptoAmountUncapped( - cryptoAmount: BigDecimal?, - cryptoCurrency: CryptoCurrency, - locale: Locale = Locale.getDefault(), - ): String { - if (cryptoAmount == null) return EMPTY_BALANCE_SIGN - - val formatter = NumberFormat.getNumberInstance(locale).apply { - maximumFractionDigits = cryptoCurrency.decimals - minimumFractionDigits = 2 - isGroupingUsed = true - roundingMode = RoundingMode.HALF_UP - } - - return formatter.format(cryptoAmount).let { - if (cryptoCurrency.symbol.isEmpty()) { - it - } else { - it + "\u2009${cryptoCurrency.symbol}" - } - } - } - - @Deprecated("Use BigDecimal.format") - fun formatCryptoFeeAmount( - cryptoAmount: BigDecimal?, - cryptoCurrency: String, - decimals: Int, - canBeLower: Boolean = false, - locale: Locale = Locale.getDefault(), - ): String { - if (cryptoAmount == null) return EMPTY_BALANCE_SIGN - - val formatter = NumberFormat.getNumberInstance(locale).apply { - maximumFractionDigits = decimals.coerceAtMost(maximumValue = 6) - minimumFractionDigits = 2 - isGroupingUsed = true - roundingMode = RoundingMode.HALF_UP - } - - val amountFormatted = if (cryptoAmount.checkCryptoThreshold()) { - buildString { - append(CAN_BE_LOWER_SIGN) - append( - formatter.format(CRYPTO_FEE_FORMAT_THRESHOLD), - ) - } - } else { - buildString { - if (canBeLower) { - append(CAN_BE_LOWER_SIGN) - } - append(formatter.format(cryptoAmount)) - } - } - - return if (cryptoCurrency.isEmpty()) { - amountFormatted - } else { - amountFormatted + "\u2009$cryptoCurrency" - } - } - - @Deprecated("Use BigDecimal.format") - fun formatCryptoAmount( - cryptoAmount: BigDecimal?, - cryptoCurrency: CryptoCurrency, - locale: Locale = Locale.getDefault(), - ): String { - return formatCryptoAmount(cryptoAmount, cryptoCurrency.symbol, cryptoCurrency.decimals, locale) - } - @Deprecated("Use BigDecimal.format") fun formatFiatAmount( fiatAmount: BigDecimal?, @@ -266,28 +131,6 @@ object BigDecimalFormatter { } } - @Deprecated("Use BigDecimal.format") - fun formatPercent( - percent: BigDecimal, - useAbsoluteValue: Boolean, - locale: Locale = Locale.getDefault(), - maxFractionDigits: Int = 2, - minFractionDigits: Int = 2, - ): String { - val formatter = NumberFormat.getPercentInstance(locale).apply { - maximumFractionDigits = maxFractionDigits - minimumFractionDigits = minFractionDigits - roundingMode = RoundingMode.HALF_UP - } - val value = if (useAbsoluteValue) percent.abs() else percent - - return formatter.format(value) - } - - fun formatWithSymbol(amount: String, symbol: String) = "$amount\u2009$symbol" - - private fun BigDecimal.isMoreThanThreshold() = this > FORMAT_THRESHOLD - private fun getCurrency(code: String): Currency { return runCatching { Currency.getInstance(code) } .getOrElse { e -> @@ -301,6 +144,4 @@ object BigDecimalFormatter { } private fun BigDecimal.checkFiatThreshold() = this > BigDecimal.ZERO && this < FIAT_FORMAT_THRESHOLD - - private fun BigDecimal.checkCryptoThreshold() = this > BigDecimal.ZERO && this < CRYPTO_FEE_FORMAT_THRESHOLD } \ No newline at end of file diff --git a/core/ui/src/test/kotlin/com/tangem/core/ui/format/bigdecimal/BigDecimalCryptoFormatTest.kt b/core/ui/src/test/kotlin/com/tangem/core/ui/format/bigdecimal/BigDecimalCryptoFormatTest.kt index cc6fa5e40e..a073607b8b 100644 --- a/core/ui/src/test/kotlin/com/tangem/core/ui/format/bigdecimal/BigDecimalCryptoFormatTest.kt +++ b/core/ui/src/test/kotlin/com/tangem/core/ui/format/bigdecimal/BigDecimalCryptoFormatTest.kt @@ -370,4 +370,38 @@ internal class BigDecimalCryptoFormatTest { Truth.assertThat(formatted) .isEqualTo("0.13".addSymbolWithSpaceLeft(symbol)) } + + // === anyDecimals() === + + @Test + fun `anyDecimals smoke`() { + val testValue = BigDecimal("0.123412341234") + + val formatted = testValue.format { + crypto( + symbol = symbol, + decimals = 5, + locale = testLocale, + ).anyDecimals() + } + + Truth.assertThat(formatted) + .isEqualTo("0.12341".addSymbolWithSpaceLeft(symbol)) + } + + @Test + fun `anyDecimals zero`() { + val testValue = BigDecimal("0.123412341234") + + val formatted = testValue.format { + crypto( + symbol = symbol, + decimals = 0, + locale = testLocale, + ).anyDecimals() + } + + Truth.assertThat(formatted) + .isEqualTo("0".addSymbolWithSpaceLeft(symbol)) + } } \ No newline at end of file diff --git a/core/utils/src/main/java/com/tangem/utils/CryptoCurrencyFormatExtensions.kt b/core/utils/src/main/java/com/tangem/utils/CryptoCurrencyFormatExtensions.kt deleted file mode 100644 index ce1a6d2470..0000000000 --- a/core/utils/src/main/java/com/tangem/utils/CryptoCurrencyFormatExtensions.kt +++ /dev/null @@ -1,43 +0,0 @@ -package com.tangem.utils - -import java.math.BigDecimal -import java.math.RoundingMode -import java.text.DecimalFormat -import java.text.NumberFormat -import java.util.Locale - -// todo determine where to place this extensions -fun BigDecimal.toFormattedString( - decimals: Int, - roundingMode: RoundingMode = RoundingMode.DOWN, - locale: Locale = Locale.getDefault(), -): String { - val formatter = NumberFormat.getInstance(locale) as? DecimalFormat - val df = formatter?.apply { - maximumFractionDigits = decimals - minimumFractionDigits = 0 - isGroupingUsed = true - this.roundingMode = roundingMode - } - return df?.format(this) ?: this.toPlainString() -} - -@Suppress("MagicNumber") -fun BigDecimal.toFormattedCurrencyString( - decimals: Int, - currency: String? = null, - roundingMode: RoundingMode = RoundingMode.DOWN, - limitNumberOfDecimals: Boolean = true, -): String { - val decimalsForRounding = if (limitNumberOfDecimals) { - if (decimals > 8) 8 else decimals - } else { - decimals - } - val formattedAmount = this.toFormattedString( - decimals = decimalsForRounding, - roundingMode = roundingMode, - ) - val formattedCurrency = currency?.let { " $it" } ?: "" - return "$formattedAmount$formattedCurrency" -} \ No newline at end of file diff --git a/domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/SendTransactionUseCase.kt b/domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/SendTransactionUseCase.kt index 3fb6ba5bc1..3d3f3063c6 100644 --- a/domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/SendTransactionUseCase.kt +++ b/domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/SendTransactionUseCase.kt @@ -11,6 +11,8 @@ import com.tangem.blockchain.extensions.Result import com.tangem.blockchain.network.ResultChecker import com.tangem.common.core.TangemSdkError import com.tangem.core.ui.extensions.wrappedList +import com.tangem.core.ui.format.bigdecimal.format +import com.tangem.core.ui.format.bigdecimal.simple import com.tangem.domain.card.repository.CardSdkConfigRepository import com.tangem.domain.common.TapWorkarounds.isStart2Coin import com.tangem.domain.common.TapWorkarounds.isTangemTwins @@ -24,7 +26,6 @@ import com.tangem.domain.transaction.error.SendTransactionError.Companion.USER_C import com.tangem.domain.walletmanager.WalletManagersFacade import com.tangem.domain.wallets.models.UserWallet import com.tangem.sdk.extensions.localizedDescriptionRes -import com.tangem.utils.toFormattedString class SendTransactionUseCase( private val demoConfig: DemoConfig, @@ -120,7 +121,7 @@ class SendTransactionUseCase( is BlockchainSdkError.WrappedTangemError -> parseWrappedError(error) is BlockchainSdkError.CreateAccountUnderfunded -> { val minAmount = error.minReserve - val minValue = minAmount.value?.toFormattedString(minAmount.decimals).orEmpty() + val minValue = minAmount.value?.format { simple(minAmount.decimals) }.orEmpty() SendTransactionError.CreateAccountUnderfunded(minValue) } else -> { diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/model/MarketsTokenDetailsModel.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/model/MarketsTokenDetailsModel.kt index b956de8357..3300e07216 100644 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/model/MarketsTokenDetailsModel.kt +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/model/MarketsTokenDetailsModel.kt @@ -17,6 +17,8 @@ import com.tangem.core.ui.components.marketprice.PriceChangeType import com.tangem.core.ui.event.consumedEvent import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.extensions.resourceReference +import com.tangem.core.ui.format.bigdecimal.format +import com.tangem.core.ui.format.bigdecimal.percent import com.tangem.core.ui.utils.BigDecimalFormatter import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase import com.tangem.domain.appcurrency.model.AppCurrency @@ -172,12 +174,7 @@ internal class MarketsTokenDetailsModel @Inject constructor( fiatCurrencySymbol = currentAppCurrency.value.symbol, ), dateTimeText = resourceReference(R.string.common_today), - priceChangePercentText = params.token.tokenQuotes.h24Percent?.let { - BigDecimalFormatter.formatPercent( - percent = it, - useAbsoluteValue = true, - ) - }, + priceChangePercentText = params.token.tokenQuotes.h24Percent?.format { percent() }, priceChangeType = params.token.tokenQuotes.h24Percent.percentChangeType(), iconUrl = params.token.imageUrl, chartState = MarketsTokenDetailsUM.ChartState( @@ -472,12 +469,7 @@ internal class MarketsTokenDetailsModel @Inject constructor( ) } ?: currentQuotes.value.getPercentByInterval(currentState.selectedInterval) - val percentText = percent?.let { - BigDecimalFormatter.formatPercent( - percent = it, - useAbsoluteValue = true, - ) - } ?: "" + val percentText = percent?.format { percent() } ?: "" state.update { stateToUpdate -> stateToUpdate.copy( diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/model/formatter/Formatters.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/model/formatter/Formatters.kt index 4c185c04e3..890606701f 100644 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/model/formatter/Formatters.kt +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/model/formatter/Formatters.kt @@ -2,6 +2,8 @@ package com.tangem.features.markets.details.impl.model.formatter import com.tangem.common.ui.charts.state.MarketChartLook import com.tangem.core.ui.components.marketprice.PriceChangeType +import com.tangem.core.ui.format.bigdecimal.format +import com.tangem.core.ui.format.bigdecimal.percent import com.tangem.core.ui.utils.BigDecimalFormatter import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.markets.PriceChangeInterval @@ -28,12 +30,7 @@ internal fun TokenQuotes.getFormattedPercentByInterval(interval: PriceChangeInte PriceChangeInterval.ALL_TIME -> allTimeChangePercent } - return percent?.let { - BigDecimalFormatter.formatPercent( - percent = it, - useAbsoluteValue = true, - ) - } ?: "" + return percent?.format { percent() } ?: "" } internal fun TokenQuotes.getPercentByInterval(interval: PriceChangeInterval): BigDecimal? { diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/token/block/impl/model/TokenMarketBlockModel.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/token/block/impl/model/TokenMarketBlockModel.kt index d0ca34298a..35346aeb94 100644 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/token/block/impl/model/TokenMarketBlockModel.kt +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/token/block/impl/model/TokenMarketBlockModel.kt @@ -11,6 +11,8 @@ import com.tangem.core.decompose.model.Model import com.tangem.core.decompose.model.ParamsContainer import com.tangem.core.decompose.navigation.Router import com.tangem.core.ui.components.marketprice.PriceChangeType +import com.tangem.core.ui.format.bigdecimal.format +import com.tangem.core.ui.format.bigdecimal.percent import com.tangem.core.ui.utils.BigDecimalFormatter import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase import com.tangem.domain.appcurrency.model.AppCurrency @@ -91,10 +93,7 @@ internal class TokenMarketBlockModel @Inject constructor( // TODO get currency from quotes use case [REDACTED_TASK_KEY] fiatCurrencySymbol = currentAppCurrency.value.symbol, ), - h24Percent = BigDecimalFormatter.formatPercent( - percent = res.priceChange, - useAbsoluteValue = true, - ), + h24Percent = res.priceChange.format { percent() }, priceChangeType = PriceChangeType.fromBigDecimal(res.priceChange), ) } diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/model/converters/MarketsTokenItemConverter.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/model/converters/MarketsTokenItemConverter.kt index f4d62e02de..41fc02dedd 100644 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/model/converters/MarketsTokenItemConverter.kt +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/model/converters/MarketsTokenItemConverter.kt @@ -8,6 +8,7 @@ import com.tangem.core.ui.components.marketprice.PriceChangeType import com.tangem.core.ui.format.bigdecimal.compact import com.tangem.core.ui.format.bigdecimal.fiat import com.tangem.core.ui.format.bigdecimal.format +import com.tangem.core.ui.format.bigdecimal.percent import com.tangem.core.ui.utils.BigDecimalFormatter import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.markets.TokenMarket @@ -149,9 +150,6 @@ internal class MarketsTokenItemConverter( TrendInterval.M1 -> tokenQuotesShort.monthChangePercent } - return BigDecimalFormatter.formatPercent( - percent = percent, - useAbsoluteValue = true, - ) + return percent.format { percent() } } } \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/recipient/SendRecipientHistoryListConverter.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/recipient/SendRecipientHistoryListConverter.kt index 8a8576775e..946bcbb17c 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/recipient/SendRecipientHistoryListConverter.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/recipient/SendRecipientHistoryListConverter.kt @@ -4,6 +4,8 @@ import com.tangem.common.extensions.isZero import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.extensions.stringReference import com.tangem.core.ui.extensions.wrappedList +import com.tangem.core.ui.format.bigdecimal.crypto +import com.tangem.core.ui.format.bigdecimal.format import com.tangem.core.ui.utils.DateTimeFormatters import com.tangem.core.ui.utils.toDateFormatWithTodayYesterday import com.tangem.core.ui.utils.toTimeFormat @@ -17,7 +19,6 @@ import com.tangem.features.send.impl.presentation.state.recipient.utils.RECENT_K import com.tangem.features.send.impl.presentation.state.recipient.utils.emptyListState import com.tangem.utils.Provider import com.tangem.utils.converter.Converter -import com.tangem.utils.toFormattedCurrencyString import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.toPersistentList @@ -76,10 +77,7 @@ internal class SendRecipientHistoryListConverter( } private fun TxHistoryItem.getAmount(cryptoCurrency: CryptoCurrency): String { - return amount.toFormattedCurrencyString( - currency = cryptoCurrency.symbol, - decimals = cryptoCurrency.decimals, - ) + return amount.format { crypto(cryptoCurrency) } } private fun TxHistoryItem.extractTimestamp(): TextReference { diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/SendNavigationButtons.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/SendNavigationButtons.kt index 864a75afa0..4c59863b0c 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/SendNavigationButtons.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/SendNavigationButtons.kt @@ -32,6 +32,9 @@ import com.tangem.core.ui.components.buttons.common.TangemButtonIconPosition import com.tangem.core.ui.components.buttons.common.TangemButtonsDefaults import com.tangem.core.ui.components.keyboardAsState import com.tangem.core.ui.extensions.* +import com.tangem.core.ui.format.bigdecimal.crypto +import com.tangem.core.ui.format.bigdecimal.fee +import com.tangem.core.ui.format.bigdecimal.format import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.utils.BigDecimalFormatter import com.tangem.features.send.impl.presentation.state.SendStates @@ -228,12 +231,14 @@ private fun SendStates.FeeState.getFiatValue() = if (isFeeConvertibleToFiat) { ) } else { val amount = fee?.amount - BigDecimalFormatter.formatCryptoFeeAmount( - cryptoAmount = amount?.value, - cryptoCurrency = amount?.currencySymbol.orEmpty(), - decimals = amount?.decimals ?: 0, - canBeLower = isFeeApproximate, - ) + amount?.value.format { + crypto( + decimals = amount?.decimals ?: 0, + symbol = amount?.currencySymbol.orEmpty(), + ).fee( + canBeLower = isFeeApproximate, + ) + } } private fun getButtonData( diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/fee/SendSpeedSelectorItem.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/fee/SendSpeedSelectorItem.kt index 712f915547..6774c2572d 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/fee/SendSpeedSelectorItem.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/fee/SendSpeedSelectorItem.kt @@ -16,6 +16,9 @@ import com.tangem.core.ui.components.RectangleShimmer import com.tangem.core.ui.components.SpacerWMax import com.tangem.core.ui.components.rows.SelectorRowItem import com.tangem.core.ui.extensions.stringReference +import com.tangem.core.ui.format.bigdecimal.crypto +import com.tangem.core.ui.format.bigdecimal.fee +import com.tangem.core.ui.format.bigdecimal.format import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.utils.BigDecimalFormatter import com.tangem.core.ui.utils.parseToBigDecimal @@ -53,12 +56,12 @@ internal fun SendSpeedSelectorItem( onSelect = onSelect, modifier = modifier, preDot = stringReference( - BigDecimalFormatter.formatCryptoFeeAmount( - cryptoAmount = amount?.value, - cryptoCurrency = amount?.currencySymbol.orEmpty(), - decimals = amount?.decimals ?: 0, - canBeLower = state.isFeeApproximate, - ), + amount?.value.format { + crypto( + symbol = amount?.currencySymbol.orEmpty(), + decimals = amount?.decimals ?: 0, + ).fee(canBeLower = state.isFeeApproximate) + }, ), postDot = if (state.isFeeConvertibleToFiat) { getFiatReference(amount?.value, state.rate, state.appCurrency) diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/send/FeeBlock.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/send/FeeBlock.kt index b396811b81..4d5830190e 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/send/FeeBlock.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/send/FeeBlock.kt @@ -18,6 +18,9 @@ import com.tangem.common.ui.amountScreen.utils.getFiatReference import com.tangem.core.ui.components.RectangleShimmer import com.tangem.core.ui.components.rows.SelectorRowItem import com.tangem.core.ui.extensions.stringReference +import com.tangem.core.ui.format.bigdecimal.crypto +import com.tangem.core.ui.format.bigdecimal.fee +import com.tangem.core.ui.format.bigdecimal.format import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemThemePreview import com.tangem.core.ui.utils.BigDecimalFormatter @@ -62,12 +65,12 @@ internal fun FeeBlock(feeState: SendStates.FeeState, isClickDisabled: Boolean, o titleRes = title, iconRes = icon, preDot = stringReference( - BigDecimalFormatter.formatCryptoFeeAmount( - cryptoAmount = feeAmount?.value, - cryptoCurrency = feeAmount?.currencySymbol.orEmpty(), - decimals = feeAmount?.decimals ?: 0, - canBeLower = feeState.isFeeApproximate, - ), + feeAmount?.value.format { + crypto( + symbol = feeAmount?.currencySymbol.orEmpty(), + decimals = feeAmount?.decimals ?: 0, + ).fee(canBeLower = feeState.isFeeApproximate) + }, ), postDot = if (feeState.isFeeConvertibleToFiat) { getFiatReference(feeAmount?.value, feeState.rate, feeState.appCurrency) diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/converters/BalanceItemConverter.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/converters/BalanceItemConverter.kt index e08e0a26d1..3268482b57 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/converters/BalanceItemConverter.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/converters/BalanceItemConverter.kt @@ -1,6 +1,8 @@ package com.tangem.features.staking.impl.presentation.state.converters import com.tangem.core.ui.extensions.* +import com.tangem.core.ui.format.bigdecimal.crypto +import com.tangem.core.ui.format.bigdecimal.format import com.tangem.core.ui.utils.BigDecimalFormatter import com.tangem.core.ui.utils.parseBigDecimal import com.tangem.domain.appcurrency.model.AppCurrency @@ -47,10 +49,7 @@ internal class BalanceItemConverter( cryptoValue = cryptoAmount.parseBigDecimal(cryptoCurrency.decimals), cryptoDecimal = cryptoAmount, cryptoAmount = stringReference( - BigDecimalFormatter.formatCryptoAmount( - cryptoAmount = cryptoAmount, - cryptoCurrency = cryptoCurrency, - ), + cryptoAmount.format { crypto(cryptoCurrency) }, ), fiatAmount = stringReference( BigDecimalFormatter.formatFiatAmount( diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/converters/RewardsValidatorStateConverter.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/converters/RewardsValidatorStateConverter.kt index aca5b46db7..b03ba279ed 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/converters/RewardsValidatorStateConverter.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/converters/RewardsValidatorStateConverter.kt @@ -1,6 +1,8 @@ package com.tangem.features.staking.impl.presentation.state.converters import com.tangem.core.ui.extensions.stringReference +import com.tangem.core.ui.format.bigdecimal.crypto +import com.tangem.core.ui.format.bigdecimal.format import com.tangem.core.ui.utils.BigDecimalFormatter import com.tangem.core.ui.utils.parseBigDecimal import com.tangem.domain.appcurrency.model.AppCurrency @@ -65,10 +67,9 @@ internal class RewardsValidatorStateConverter( val appCurrency = appCurrencyProvider() val cryptoCurrency = cryptoCurrencyStatus.currency val cryptoAmount = stringReference( - BigDecimalFormatter.formatCryptoAmount( - cryptoAmount = cryptoValue, - cryptoCurrency = cryptoCurrency, - ), + cryptoValue.format { + crypto(cryptoCurrency) + }, ) val fiatAmount = stringReference( BigDecimalFormatter.formatFiatAmount( diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/converters/YieldBalancesConverter.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/converters/YieldBalancesConverter.kt index 53674f1c51..00bc11c717 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/converters/YieldBalancesConverter.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/converters/YieldBalancesConverter.kt @@ -1,6 +1,8 @@ package com.tangem.features.staking.impl.presentation.state.converters import com.tangem.common.extensions.isZero +import com.tangem.core.ui.format.bigdecimal.crypto +import com.tangem.core.ui.format.bigdecimal.format import com.tangem.core.ui.utils.BigDecimalFormatter import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.staking.model.stakekit.* @@ -41,10 +43,7 @@ internal class YieldBalancesConverter( } val (type, isActionable) = getRewardBlockType() InnerYieldBalanceState.Data( - rewardsCrypto = BigDecimalFormatter.formatCryptoAmount( - cryptoAmount = cryptoRewardsValue, - cryptoCurrency = cryptoCurrency, - ), + rewardsCrypto = cryptoRewardsValue.format { crypto(cryptoCurrency) }, rewardsFiat = BigDecimalFormatter.formatFiatAmount( fiatAmount = fiatRewardsValue, fiatCurrencyCode = appCurrency.code, diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetInitialDataStateTransformer.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetInitialDataStateTransformer.kt index b962abda5b..c3ba140e75 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetInitialDataStateTransformer.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetInitialDataStateTransformer.kt @@ -6,8 +6,10 @@ import com.tangem.common.ui.amountScreen.models.AmountState import com.tangem.core.ui.components.currency.icon.converter.CryptoCurrencyToIconStateConverter import com.tangem.core.ui.components.list.RoundedListWithDividersItemData import com.tangem.core.ui.extensions.* +import com.tangem.core.ui.format.bigdecimal.crypto +import com.tangem.core.ui.format.bigdecimal.format +import com.tangem.core.ui.format.bigdecimal.percent import com.tangem.core.ui.pullToRefresh.PullToRefreshConfig -import com.tangem.core.ui.utils.BigDecimalFormatter import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.staking.model.stakekit.BalanceItem import com.tangem.domain.staking.model.stakekit.Yield @@ -126,11 +128,7 @@ internal class SetInitialDataStateTransformer( id = R.string.staking_details_available, startText = TextReference.Res(R.string.staking_details_available), endText = TextReference.Str( - value = BigDecimalFormatter.formatCryptoAmount( - cryptoAmount = cryptoCurrencyStatus.value.amount, - cryptoCurrency = cryptoCurrencyStatus.currency.symbol, - decimals = cryptoCurrencyStatus.currency.decimals, - ), + value = cryptoCurrencyStatus.value.amount.format { crypto(cryptoCurrencyStatus.currency) }, ), isEndTextHideable = true, ) @@ -156,11 +154,7 @@ internal class SetInitialDataStateTransformer( val minimumCryptoAmount = yield.args.enter.args[Yield.Args.ArgType.AMOUNT]?.minimum ?: return null if (!isPolkadot(cryptoCurrencyStatus.currency.network.id.value)) return null - val formattedAmount = BigDecimalFormatter.formatCryptoAmount( - cryptoAmount = minimumCryptoAmount, - cryptoCurrency = cryptoCurrencyStatus.currency.symbol, - decimals = cryptoCurrencyStatus.currency.decimals, - ) + val formattedAmount = minimumCryptoAmount.format { crypto(cryptoCurrencyStatus.currency) } return RoundedListWithDividersItemData( id = R.string.staking_details_minimum_requirement, @@ -218,14 +212,8 @@ internal class SetInitialDataStateTransformer( val minApr = aprValues.min() val maxApr = aprValues.max() - val formattedMinApr = BigDecimalFormatter.formatPercent( - percent = minApr, - useAbsoluteValue = true, - ).remove("%") - val formattedMaxApr = BigDecimalFormatter.formatPercent( - percent = maxApr, - useAbsoluteValue = true, - ) + val formattedMinApr = minApr.format { percent() }.remove("%") + val formattedMaxApr = maxApr.format { percent() } if (maxApr - minApr < EQUALITY_THRESHOLD) { return stringReference("$formattedMinApr%") diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/amount/AmountRequirementStateTransformer.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/amount/AmountRequirementStateTransformer.kt index 284f9b239b..88ac2c09f5 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/amount/AmountRequirementStateTransformer.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/amount/AmountRequirementStateTransformer.kt @@ -7,7 +7,8 @@ import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.extensions.isNullOrEmpty import com.tangem.core.ui.extensions.resourceReference import com.tangem.core.ui.extensions.wrappedList -import com.tangem.core.ui.utils.BigDecimalFormatter +import com.tangem.core.ui.format.bigdecimal.crypto +import com.tangem.core.ui.format.bigdecimal.format import com.tangem.core.ui.utils.parseBigDecimal import com.tangem.domain.staking.model.stakekit.AddressArgument import com.tangem.domain.staking.model.stakekit.Yield @@ -55,11 +56,9 @@ internal class AmountRequirementStateTransformer( isRequirementError -> resourceReference( R.string.staking_amount_requirement_error, wrappedList( - BigDecimalFormatter.formatCryptoAmount( - amountRequirements.minimum, - cryptoCurrencyStatus.currency.symbol, - cryptoCurrencyStatus.currency.decimals, - ), + amountRequirements.minimum.format { + crypto(cryptoCurrencyStatus.currency) + }, ), ) isIntegerOnlyError -> resourceReference( diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/approval/ShowApprovalBottomSheetTransformer.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/approval/ShowApprovalBottomSheetTransformer.kt index ca32d6c1be..ea5f081cfd 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/approval/ShowApprovalBottomSheetTransformer.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/approval/ShowApprovalBottomSheetTransformer.kt @@ -5,6 +5,8 @@ import com.tangem.common.ui.bottomsheet.permission.state.* import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig import com.tangem.core.ui.extensions.resourceReference import com.tangem.core.ui.extensions.wrappedList +import com.tangem.core.ui.format.bigdecimal.crypto +import com.tangem.core.ui.format.bigdecimal.format import com.tangem.core.ui.utils.BigDecimalFormatter import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.tokens.model.CryptoCurrencyStatus @@ -33,11 +35,9 @@ internal class ShowApprovalBottomSheetTransformer( val walletAddress = cryptoCurrencyValue.networkAddress?.defaultAddress?.value.orEmpty() val validatorAddress = validatorState.chosenValidator.address - val feeCryptoValue = BigDecimalFormatter.formatCryptoAmount( - cryptoAmount = fee.amount.value, - cryptoCurrency = fee.amount.currencySymbol, - decimals = fee.amount.decimals, - ) + val feeCryptoValue = fee.amount.value.format { + crypto(fee.amount.currencySymbol, fee.amount.decimals) + } val feeFiatValue = BigDecimalFormatter.formatFiatAmount( fiatAmount = feeCryptoCurrencyStatus?.value?.fiatRate?.multiply(fee.amount.value), fiatCurrencyCode = appCurrencyProvider().code, diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingClaimRewardsValidatorContent.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingClaimRewardsValidatorContent.kt index fecd9f9cb6..e42c4e7c6c 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingClaimRewardsValidatorContent.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingClaimRewardsValidatorContent.kt @@ -12,8 +12,9 @@ import androidx.compose.ui.Modifier import com.tangem.core.ui.components.inputrow.InputRowImageInfo import com.tangem.core.ui.decorations.roundedShapeItemDecoration import com.tangem.core.ui.extensions.* +import com.tangem.core.ui.format.bigdecimal.format +import com.tangem.core.ui.format.bigdecimal.percent import com.tangem.core.ui.res.TangemTheme -import com.tangem.core.ui.utils.BigDecimalFormatter import com.tangem.features.staking.impl.R import com.tangem.features.staking.impl.presentation.state.StakingStates import com.tangem.features.staking.impl.presentation.viewmodel.StakingClickIntents @@ -41,10 +42,7 @@ internal fun StakingClaimRewardsValidatorContent( annotatedReference { appendSpace() appendColored( - text = BigDecimalFormatter.formatPercent( - percent = item.validator?.apr.orZero(), - useAbsoluteValue = true, - ), + text = item.validator?.apr.orZero().format { percent() }, color = TangemTheme.colors.text.accent, ) }, diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingInitialInfoContent.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingInitialInfoContent.kt index 4a5d106633..2895d3619c 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingInitialInfoContent.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingInitialInfoContent.kt @@ -41,11 +41,12 @@ import com.tangem.core.ui.components.inputrow.InputRowImageInfo import com.tangem.core.ui.components.list.roundedListWithDividersItems import com.tangem.core.ui.decorations.roundedShapeItemDecoration import com.tangem.core.ui.extensions.* +import com.tangem.core.ui.format.bigdecimal.format +import com.tangem.core.ui.format.bigdecimal.percent import com.tangem.core.ui.pullToRefresh.PullToRefreshConfig import com.tangem.core.ui.res.TangemColorPalette import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemThemePreview -import com.tangem.core.ui.utils.BigDecimalFormatter import com.tangem.domain.staking.model.stakekit.BalanceType import com.tangem.domain.staking.model.stakekit.RewardBlockType import com.tangem.features.staking.impl.R @@ -311,10 +312,7 @@ private fun BalanceState.getAprText() = combinedReference( annotatedReference { appendSpace() appendColored( - text = BigDecimalFormatter.formatPercent( - percent = validator?.apr.orZero(), - useAbsoluteValue = true, - ), + text = validator?.apr.orZero().format { percent() }, color = TangemTheme.colors.text.accent, ) }, diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingValidatorListContent.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingValidatorListContent.kt index 692f5b3050..1bcd5c9c60 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingValidatorListContent.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingValidatorListContent.kt @@ -24,9 +24,10 @@ import androidx.compose.ui.unit.dp import com.tangem.core.ui.components.inputrow.InputRowImageSelector import com.tangem.core.ui.decorations.roundedShapeItemDecoration import com.tangem.core.ui.extensions.* +import com.tangem.core.ui.format.bigdecimal.format +import com.tangem.core.ui.format.bigdecimal.percent import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemThemePreview -import com.tangem.core.ui.utils.BigDecimalFormatter import com.tangem.features.staking.impl.R import com.tangem.features.staking.impl.presentation.state.StakingStates import com.tangem.features.staking.impl.presentation.state.previewdata.ValidatorStatePreviewData @@ -67,7 +68,7 @@ internal fun StakingValidatorListContent( annotatedReference { appendSpace() appendColored( - text = BigDecimalFormatter.formatPercent(item.apr.orZero(), true), + text = item.apr.orZero().format { percent() }, color = TangemTheme.colors.text.accent, ) }, diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/block/StakingFeeBlock.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/block/StakingFeeBlock.kt index e666de9908..8817faede7 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/block/StakingFeeBlock.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/block/StakingFeeBlock.kt @@ -21,6 +21,9 @@ import com.tangem.common.ui.amountScreen.utils.getFiatReference import com.tangem.core.ui.components.RectangleShimmer import com.tangem.core.ui.components.rows.SelectorRowItem import com.tangem.core.ui.extensions.stringReference +import com.tangem.core.ui.format.bigdecimal.crypto +import com.tangem.core.ui.format.bigdecimal.fee +import com.tangem.core.ui.format.bigdecimal.format import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemThemePreview import com.tangem.core.ui.utils.BigDecimalFormatter @@ -56,12 +59,12 @@ internal fun StakingFeeBlock(feeState: FeeState, isTransactionSent: Boolean) { titleRes = R.string.common_fee_selector_option_market, iconRes = R.drawable.ic_bird_24, preDot = stringReference( - BigDecimalFormatter.formatCryptoFeeAmount( - cryptoAmount = feeAmount?.value, - cryptoCurrency = feeAmount?.currencySymbol.orEmpty(), - decimals = feeAmount?.decimals ?: 0, - canBeLower = feeState.isFeeApproximate, - ), + feeAmount?.value.format { + crypto( + symbol = feeAmount?.currencySymbol.orEmpty(), + decimals = feeAmount?.decimals ?: 0, + ).fee(canBeLower = feeState.isFeeApproximate) + }, ), postDot = if (feeState.isFeeConvertibleToFiat) { getFiatReference(feeAmount?.value, feeState.rate, feeState.appCurrency) diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/block/ValidatorBlock.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/block/ValidatorBlock.kt index 2da1751f34..3d5cc0310a 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/block/ValidatorBlock.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/block/ValidatorBlock.kt @@ -12,8 +12,9 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import com.tangem.core.ui.components.inputrow.InputRowImageInfo import com.tangem.core.ui.extensions.* +import com.tangem.core.ui.format.bigdecimal.format +import com.tangem.core.ui.format.bigdecimal.percent import com.tangem.core.ui.res.TangemTheme -import com.tangem.core.ui.utils.BigDecimalFormatter import com.tangem.features.staking.impl.R import com.tangem.features.staking.impl.presentation.state.StakingStates import com.tangem.features.staking.impl.presentation.ui.ValidatorImagePlaceholder @@ -43,7 +44,7 @@ internal fun ValidatorBlock(validatorState: StakingStates.ValidatorState, isClic append(resourceReference(R.string.staking_details_apr).resolveReference()) appendSpace() appendColored( - text = BigDecimalFormatter.formatPercent(state.chosenValidator.apr.orZero(), true), + text = state.chosenValidator.apr.orZero().format { percent() }, color = TangemTheme.colors.text.accent, ) }, diff --git a/features/swap/domain/models/src/main/java/com/tangem/feature/swap/domain/models/SwapAmount.kt b/features/swap/domain/models/src/main/java/com/tangem/feature/swap/domain/models/SwapAmount.kt index 4b451b4dc3..aca86a3d52 100644 --- a/features/swap/domain/models/src/main/java/com/tangem/feature/swap/domain/models/SwapAmount.kt +++ b/features/swap/domain/models/src/main/java/com/tangem/feature/swap/domain/models/SwapAmount.kt @@ -1,6 +1,5 @@ package com.tangem.feature.swap.domain.models -import com.tangem.utils.toFormattedString import java.math.BigDecimal import java.math.RoundingMode @@ -16,10 +15,6 @@ data class SwapAmount( } } -fun SwapAmount.formatToUIRepresentation(): String { - return value.toFormattedString(decimals = decimals) -} - fun createFromAmountWithoutOffset(amountWithoutOffset: String, decimals: Int): SwapAmount { return SwapAmount( value = requireNotNull( diff --git a/features/swap/domain/models/src/main/java/com/tangem/feature/swap/domain/models/ui/AmountFormatter.kt b/features/swap/domain/models/src/main/java/com/tangem/feature/swap/domain/models/ui/AmountFormatter.kt index 01914d161b..73a7999a48 100644 --- a/features/swap/domain/models/src/main/java/com/tangem/feature/swap/domain/models/ui/AmountFormatter.kt +++ b/features/swap/domain/models/src/main/java/com/tangem/feature/swap/domain/models/ui/AmountFormatter.kt @@ -1,10 +1,9 @@ package com.tangem.feature.swap.domain.models.ui import com.tangem.feature.swap.domain.models.SwapAmount -import com.tangem.utils.toFormattedCurrencyString import java.math.BigDecimal -class AmountFormatter { +interface AmountFormatter { /** * Use to convert crypto amount [SwapAmount] to UI representation @@ -13,9 +12,7 @@ class AmountFormatter { * @param currency currency symbol * @return formatted [String] */ - fun formatSwapAmountToUI(swapAmount: SwapAmount, currency: String): String { - return swapAmount.value.toFormattedCurrencyString(swapAmount.decimals, currency) - } + fun formatSwapAmountToUI(swapAmount: SwapAmount, currency: String): String /** * Use to convert ONLY crypto amount [BigDecimal] to UI representation @@ -25,7 +22,5 @@ class AmountFormatter { * @param currency * @return formatted [String] */ - fun formatBigDecimalAmountToUI(amount: BigDecimal, decimals: Int, currency: String? = null): String { - return amount.toFormattedCurrencyString(decimals, currency) - } + fun formatBigDecimalAmountToUI(amount: BigDecimal, decimals: Int, currency: String? = null): String } \ No newline at end of file diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt index b77df21551..63b9c43970 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt @@ -71,6 +71,7 @@ internal class SwapInteractorImpl @AssistedInject constructor( private val validateTransactionUseCase: ValidateTransactionUseCase, private val estimateFeeUseCase: EstimateFeeUseCase, private val getUserWalletUseCase: GetUserWalletUseCase, + private val amountFormatter: AmountFormatter, @Assisted private val userWalletId: UserWalletId, ) : SwapInteractor { @@ -79,7 +80,6 @@ internal class SwapInteractorImpl @AssistedInject constructor( } private val swapCurrencyConverter = SwapCurrencyConverter() - private val amountFormatter = AmountFormatter() private val hundredPercent = BigInteger("100") private val userWallet diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/converters/TokensDataConverter.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/converters/TokensDataConverter.kt index 390b8e7c7e..49a1dbc25b 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/converters/TokensDataConverter.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/converters/TokensDataConverter.kt @@ -2,6 +2,8 @@ package com.tangem.feature.swap.converters import com.tangem.core.ui.components.currency.icon.CurrencyIconState import com.tangem.core.ui.extensions.* +import com.tangem.core.ui.format.bigdecimal.crypto +import com.tangem.core.ui.format.bigdecimal.format import com.tangem.core.ui.utils.BigDecimalFormatter import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.tokens.model.CryptoCurrency @@ -103,11 +105,9 @@ class TokensDataConverter( } private fun formatCryptoAmount(cryptoCurrencyStatus: CryptoCurrencyStatus): String { - return BigDecimalFormatter.formatCryptoAmount( - cryptoCurrencyStatus.value.amount, - cryptoCurrencyStatus.currency.symbol, - cryptoCurrencyStatus.currency.decimals, - ) + return cryptoCurrencyStatus.value.amount.format { + crypto(cryptoCurrencyStatus.currency) + } } private fun formatFiatAmount(cryptoCurrencyStatus: CryptoCurrencyStatus, appCurrency: AppCurrency): String { diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/di/SwapSingletonModule.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/di/SwapSingletonModule.kt new file mode 100644 index 0000000000..d265dc06e2 --- /dev/null +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/di/SwapSingletonModule.kt @@ -0,0 +1,18 @@ +package com.tangem.feature.swap.di + +import com.tangem.feature.swap.di.impl.DefaultAmountFormatter +import com.tangem.feature.swap.domain.models.ui.AmountFormatter +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent + +@Module +@InstallIn(SingletonComponent::class) +class SwapSingletonModule { + + @Provides + fun provideAmountFormatter(): AmountFormatter { + return DefaultAmountFormatter() + } +} \ No newline at end of file diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/di/impl/DefaultAmountFormatter.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/di/impl/DefaultAmountFormatter.kt new file mode 100644 index 0000000000..91d8dd1821 --- /dev/null +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/di/impl/DefaultAmountFormatter.kt @@ -0,0 +1,18 @@ +package com.tangem.feature.swap.di.impl + +import com.tangem.core.ui.format.bigdecimal.crypto +import com.tangem.core.ui.format.bigdecimal.format +import com.tangem.feature.swap.domain.models.SwapAmount +import com.tangem.feature.swap.domain.models.ui.AmountFormatter +import java.math.BigDecimal + +class DefaultAmountFormatter : AmountFormatter { + + override fun formatSwapAmountToUI(swapAmount: SwapAmount, currency: String): String { + return swapAmount.value.format { crypto(symbol = currency, decimals = swapAmount.decimals) } + } + + override fun formatBigDecimalAmountToUI(amount: BigDecimal, decimals: Int, currency: String?): String { + return amount.format { crypto(symbol = currency.orEmpty(), decimals = decimals) } + } +} \ No newline at end of file diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt index 96ae124966..6fbbf57b82 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt @@ -10,6 +10,10 @@ import com.tangem.core.ui.components.notifications.NotificationConfig import com.tangem.core.ui.event.consumedEvent import com.tangem.core.ui.event.triggeredEvent import com.tangem.core.ui.extensions.* +import com.tangem.core.ui.format.bigdecimal.anyDecimals +import com.tangem.core.ui.format.bigdecimal.crypto +import com.tangem.core.ui.format.bigdecimal.format +import com.tangem.core.ui.format.bigdecimal.uncapped import com.tangem.core.ui.utils.BigDecimalFormatter import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.tokens.model.CryptoCurrency @@ -19,12 +23,12 @@ import com.tangem.feature.swap.converters.TokensDataConverter import com.tangem.feature.swap.domain.models.ExpressDataError import com.tangem.feature.swap.domain.models.SwapAmount import com.tangem.feature.swap.domain.models.domain.* -import com.tangem.feature.swap.domain.models.formatToUIRepresentation import com.tangem.feature.swap.domain.models.ui.* import com.tangem.feature.swap.models.* import com.tangem.feature.swap.models.states.* import com.tangem.feature.swap.models.states.events.SwapEvent import com.tangem.feature.swap.presentation.R +import com.tangem.feature.swap.utils.formatToUIRepresentation import com.tangem.feature.swap.utils.getExpressErrorMessage import com.tangem.feature.swap.utils.getExpressErrorTitle import com.tangem.feature.swap.viewmodels.SwapProcessDataState @@ -469,10 +473,8 @@ internal class StateBuilder( domainWarning: Warning.ExistentialDepositWarning, ): SwapWarning { val fromCurrency = quoteModel.fromTokenInfo.cryptoCurrencyStatus.currency - val deposit = BigDecimalFormatter.formatCryptoAmountUncapped( - cryptoAmount = domainWarning.existentialDeposit, - cryptoCurrency = fromCurrency, - ) + val deposit = domainWarning.existentialDeposit.format { crypto(fromCurrency).uncapped() } + return SwapWarning.GeneralError( NotificationConfig( title = resourceReference(R.string.send_notification_existential_deposit_title), @@ -1546,8 +1548,12 @@ internal class StateBuilder( toTokenInfo.cryptoCurrencyStatus.currency.decimals, ) val fromCurrencySymbol = fromTokenInfo.cryptoCurrencyStatus.currency.symbol - val toCurrencySymbol = toTokenInfo.cryptoCurrencyStatus.currency.symbol - val rateString = "1 $fromCurrencySymbol ≈ $rate $toCurrencySymbol" + val rateString = buildString { + append(BigDecimal.ONE.format { crypto(symbol = fromCurrencySymbol, decimals = 0).anyDecimals() }) + append(" ≈ ") + append(rate.format { crypto(toTokenInfo.cryptoCurrencyStatus.currency) }) + } + // val rateString = "1 $fromCurrencySymbol ≈ $rate $toCurrencySymbol" val badge = if (isRecommended) { ProviderState.AdditionalBadge.Recommended } else if (isNeedBestRateBadge && isBestRate) { @@ -1628,7 +1634,7 @@ internal class StateBuilder( private fun CryptoCurrencyStatus.getFormattedAmount(isNeedSymbol: Boolean): String { val amount = value.amount ?: return DASH_SIGN val symbol = if (isNeedSymbol) currency.symbol else "" - return BigDecimalFormatter.formatCryptoAmount(amount, symbol, currency.decimals) + return amount.format { crypto(symbol, currency.decimals) } } @Suppress("UnusedPrivateMember") @@ -1646,7 +1652,7 @@ internal class StateBuilder( } private fun SwapAmount.getFormattedCryptoAmount(token: CryptoCurrency): String { - return BigDecimalFormatter.formatCryptoAmount(value, token.symbol, token.decimals) + return value.format { crypto(token) } } private fun BigDecimal.calculateRate(to: BigDecimal, decimals: Int): BigDecimal { diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/utils/SwapUtils.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/utils/SwapUtils.kt index e288546e37..44c918c906 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/utils/SwapUtils.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/utils/SwapUtils.kt @@ -3,7 +3,10 @@ package com.tangem.feature.swap.utils import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.extensions.resourceReference import com.tangem.core.ui.extensions.wrappedList +import com.tangem.core.ui.format.bigdecimal.format +import com.tangem.core.ui.format.bigdecimal.simple import com.tangem.feature.swap.domain.models.ExpressDataError +import com.tangem.feature.swap.domain.models.SwapAmount import com.tangem.feature.swap.presentation.R internal fun getExpressErrorMessage(expressDataError: ExpressDataError): TextReference { @@ -38,4 +41,8 @@ internal fun getExpressErrorTitle(expressDataError: ExpressDataError): TextRefer is ExpressDataError.UnknownError -> resourceReference(R.string.common_error) else -> resourceReference(R.string.warning_express_refresh_required_title) } +} + +internal fun SwapAmount.formatToUIRepresentation(): String { + return value.format { simple(decimals = decimals) } } \ No newline at end of file diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/viewmodels/SwapViewModel.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/viewmodels/SwapViewModel.kt index 24e23b5336..0003e45237 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/viewmodels/SwapViewModel.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/viewmodels/SwapViewModel.kt @@ -40,7 +40,6 @@ import com.tangem.feature.swap.domain.models.ExpressDataError import com.tangem.feature.swap.domain.models.ExpressException import com.tangem.feature.swap.domain.models.SwapAmount import com.tangem.feature.swap.domain.models.domain.* -import com.tangem.feature.swap.domain.models.formatToUIRepresentation import com.tangem.feature.swap.domain.models.ui.* import com.tangem.feature.swap.models.SwapStateHolder import com.tangem.feature.swap.models.SwapWarning @@ -49,6 +48,7 @@ import com.tangem.feature.swap.presentation.R import com.tangem.feature.swap.router.SwapNavScreen import com.tangem.feature.swap.router.SwapRouter import com.tangem.feature.swap.ui.StateBuilder +import com.tangem.feature.swap.utils.formatToUIRepresentation import com.tangem.utils.Provider import com.tangem.utils.coroutines.* import com.tangem.utils.isNullOrZero diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsBalanceSelectStateConverter.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsBalanceSelectStateConverter.kt index 0625d38565..4a76d6f1c9 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsBalanceSelectStateConverter.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsBalanceSelectStateConverter.kt @@ -1,5 +1,7 @@ package com.tangem.feature.tokendetails.presentation.tokendetails.state.factory +import com.tangem.core.ui.format.bigdecimal.crypto +import com.tangem.core.ui.format.bigdecimal.format import com.tangem.core.ui.utils.BigDecimalFormatter import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.staking.model.stakekit.YieldBalance @@ -72,6 +74,6 @@ internal class TokenDetailsBalanceSelectStateConverter( val amount = status.value.amount ?: return BigDecimalFormatter.EMPTY_BALANCE_SIGN val totalAmount = amount.getBalance(selectedBalanceType, stakingCryptoAmount) - return BigDecimalFormatter.formatCryptoAmount(totalAmount, status.currency.symbol, status.currency.decimals) + return totalAmount.format { crypto(status.currency) } } } \ No newline at end of file diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsLoadedBalanceConverter.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsLoadedBalanceConverter.kt index 4ddc9c283e..ae45ac9884 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsLoadedBalanceConverter.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsLoadedBalanceConverter.kt @@ -10,6 +10,9 @@ import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.extensions.resourceReference import com.tangem.core.ui.extensions.stringReference import com.tangem.core.ui.extensions.wrappedList +import com.tangem.core.ui.format.bigdecimal.crypto +import com.tangem.core.ui.format.bigdecimal.format +import com.tangem.core.ui.format.bigdecimal.percent import com.tangem.core.ui.utils.BigDecimalFormatter import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.staking.model.StakingAvailability @@ -215,10 +218,7 @@ internal class TokenDetailsLoadedBalanceConverter( stakingEntryInfo: StakingEntryInfo, iconState: IconState, ): StakingBlockUM.StakeAvailable { - val apr = BigDecimalFormatter.formatPercent( - percent = stakingEntryInfo.apr, - useAbsoluteValue = true, - ) + val apr = stakingEntryInfo.apr.format { percent() } return StakingBlockUM.StakeAvailable( titleText = resourceReference( id = R.string.token_details_staking_block_title, @@ -243,7 +243,7 @@ internal class TokenDetailsLoadedBalanceConverter( cryptoAmount = stakingCryptoAmount, fiatAmount = stakingFiatAmount, cryptoValue = stringReference( - BigDecimalFormatter.formatCryptoAmount(stakingCryptoAmount, symbol, decimals), + stakingCryptoAmount.format { crypto(symbol = symbol, decimals = decimals) }, ), fiatValue = stringReference( BigDecimalFormatter.formatFiatAmount( @@ -275,10 +275,7 @@ internal class TokenDetailsLoadedBalanceConverter( private fun formatPriceChange(status: CryptoCurrencyStatus.Value): String { val priceChange = status.priceChange ?: return BigDecimalFormatter.EMPTY_BALANCE_SIGN - return BigDecimalFormatter.formatPercent( - percent = priceChange, - useAbsoluteValue = true, - ) + return priceChange.format { percent() } } private fun formatPrice(status: CryptoCurrencyStatus.Value, appCurrency: AppCurrency): String { @@ -315,7 +312,7 @@ internal class TokenDetailsLoadedBalanceConverter( val amount = status.value.amount ?: return BigDecimalFormatter.EMPTY_BALANCE_SIGN val totalAmount = amount.getBalance(selectedBalanceType, stakingCryptoAmount) - return BigDecimalFormatter.formatCryptoAmount(totalAmount, status.currency.symbol, status.currency.decimals) + return totalAmount.format { crypto(status.currency) } } private fun getRewardText(status: CryptoCurrencyStatus, stakingRewardAmount: BigDecimal?): TextReference { diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsNotificationConverter.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsNotificationConverter.kt index 0722f5a792..86aae8128f 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsNotificationConverter.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsNotificationConverter.kt @@ -2,8 +2,10 @@ package com.tangem.feature.tokendetails.presentation.tokendetails.state.factory import com.tangem.blockchain.common.Blockchain import com.tangem.blockchainsdk.utils.fromNetworkId -import com.tangem.core.ui.utils.BigDecimalFormatter import com.tangem.core.ui.extensions.resourceReference +import com.tangem.core.ui.format.bigdecimal.crypto +import com.tangem.core.ui.format.bigdecimal.format +import com.tangem.core.ui.format.bigdecimal.shorted import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.tokens.model.warnings.CryptoCurrencyWarning import com.tangem.domain.tokens.model.warnings.HederaWarnings @@ -78,11 +80,9 @@ internal class TokenDetailsNotificationConverter( CryptoCurrencyWarning.SomeNetworksUnreachable -> NetworksUnreachable is CryptoCurrencyWarning.SomeNetworksNoAccount -> NetworksNoAccount( network = warning.amountCurrency.name, - amount = BigDecimalFormatter.formatCryptoAmount( - cryptoAmount = warning.amountToCreateAccount, - cryptoCurrency = "", - decimals = warning.amountCurrency.decimals, - ), + amount = warning.amountToCreateAccount.format { + crypto(symbol = "", decimals = warning.amountCurrency.decimals) + }, symbol = warning.amountCurrency.symbol, ) is CryptoCurrencyWarning.TopUpWithoutReserve -> TopUpWithoutReserve @@ -105,11 +105,7 @@ internal class TokenDetailsNotificationConverter( ) is HederaWarnings.AssociateWarningWithFee -> HederaAssociateWarning( currency = warning.currency, - fee = BigDecimalFormatter.formatCryptoAmount( - cryptoAmount = warning.fee, - cryptoCurrency = "", - decimals = warning.feeCurrencyDecimals, - ), + fee = warning.fee.format { crypto(symbol = "", decimals = warning.feeCurrencyDecimals) }, feeCurrencySymbol = warning.feeCurrencySymbol, onAssociateClick = clickIntents::onAssociateClick, ) @@ -127,11 +123,7 @@ internal class TokenDetailsNotificationConverter( } private fun formatMana(amount: BigDecimal): String { - return BigDecimalFormatter.formatCryptoAmountShorted( - cryptoAmount = amount, - cryptoCurrency = "", - decimals = Blockchain.Koinos.decimals(), - ) + return amount.format { crypto("", Blockchain.Koinos.decimals()).shorted() } } // workaround for networks that users have misunderstanding diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsSwapTransactionsStateConverter.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsSwapTransactionsStateConverter.kt index 6a8a7087ed..ca04809b0a 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsSwapTransactionsStateConverter.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsSwapTransactionsStateConverter.kt @@ -3,6 +3,8 @@ package com.tangem.feature.tokendetails.presentation.tokendetails.state.factory import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.core.ui.components.currency.icon.converter.CryptoCurrencyToIconStateConverter import com.tangem.core.ui.extensions.TextReference +import com.tangem.core.ui.format.bigdecimal.crypto +import com.tangem.core.ui.format.bigdecimal.format import com.tangem.core.ui.utils.BigDecimalFormatter import com.tangem.core.ui.utils.toDateFormatWithTodayYesterday import com.tangem.core.ui.utils.toTimeFormat @@ -80,17 +82,11 @@ internal class TokenDetailsSwapTransactionsStateConverter( activeStatus = transaction.status?.status, notification = notifications, toCryptoCurrency = toCryptoCurrency, - toCryptoAmount = BigDecimalFormatter.formatCryptoAmount( - cryptoAmount = toAmount, - cryptoCurrency = toCryptoCurrency, - ), + toCryptoAmount = toAmount.format { crypto(toCryptoCurrency) }, toFiatAmount = getFiatAmount(toFiatAmount), toCurrencyIcon = iconStateConverter.convert(toCryptoCurrency), fromCryptoCurrency = fromCryptoCurrency, - fromCryptoAmount = BigDecimalFormatter.formatCryptoAmount( - cryptoAmount = fromAmount, - cryptoCurrency = fromCryptoCurrency, - ), + fromCryptoAmount = fromAmount.format { crypto(fromCryptoCurrency) }, fromFiatAmount = getFiatAmount(fromFiatAmount), fromCurrencyIcon = iconStateConverter.convert(fromCryptoCurrency), showProviderLink = showProviderLink, diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/txhistory/TokenDetailsTxHistoryTransactionStateConverter.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/txhistory/TokenDetailsTxHistoryTransactionStateConverter.kt index 0d0b59e987..4756f994ca 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/txhistory/TokenDetailsTxHistoryTransactionStateConverter.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/txhistory/TokenDetailsTxHistoryTransactionStateConverter.kt @@ -7,6 +7,8 @@ import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.extensions.resourceReference import com.tangem.core.ui.extensions.stringReference import com.tangem.core.ui.extensions.wrappedList +import com.tangem.core.ui.format.bigdecimal.crypto +import com.tangem.core.ui.format.bigdecimal.format import com.tangem.core.ui.utils.toTimeFormat import com.tangem.domain.txhistory.models.TxHistoryItem import com.tangem.domain.txhistory.models.TxHistoryItem.* @@ -16,7 +18,6 @@ import com.tangem.utils.StringsSigns.MINUS import com.tangem.utils.StringsSigns.PLUS import com.tangem.utils.converter.Converter import com.tangem.utils.toBriefAddressFormat -import com.tangem.utils.toFormattedCurrencyString internal class TokenDetailsTxHistoryTransactionStateConverter( private val symbol: String, @@ -129,6 +130,6 @@ internal class TokenDetailsTxHistoryTransactionStateConverter( this.amount.isZero() -> "" else -> if (isOutgoing) MINUS else PLUS } - return prefix + amount.toFormattedCurrencyString(currency = symbol, decimals = decimals) + return prefix + amount.format { crypto(symbol = symbol, decimals = decimals) } } } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/WalletAdditionalInfoFactory.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/WalletAdditionalInfoFactory.kt index 3de82a88d3..f258bf29b6 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/WalletAdditionalInfoFactory.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/WalletAdditionalInfoFactory.kt @@ -4,7 +4,8 @@ import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.extensions.plus import com.tangem.core.ui.extensions.stringReference import com.tangem.core.ui.extensions.wrappedList -import com.tangem.core.ui.utils.BigDecimalFormatter +import com.tangem.core.ui.format.bigdecimal.crypto +import com.tangem.core.ui.format.bigdecimal.format import com.tangem.domain.common.util.cardTypesResolver import com.tangem.domain.common.util.getCardsCount import com.tangem.domain.wallets.models.UserWallet @@ -97,13 +98,7 @@ internal object WalletAdditionalInfoFactory { WalletAdditionalInfo(hideable = false, content = TextReference.Res(R.string.common_locked)) } else { val blockchain = scanResponse.cardTypesResolver.getBlockchain() - val amount = currencyAmount?.let { - BigDecimalFormatter.formatCryptoAmount( - cryptoAmount = it, - cryptoCurrency = blockchain.currency, - decimals = blockchain.decimals(), - ) - } + val amount = currencyAmount?.format { crypto(blockchain.currency, blockchain.decimals()) } WalletAdditionalInfo(hideable = true, content = TextReference.Str(value = amount.orEmpty())) } diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/SetBalancesAndLimitsTransformer.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/SetBalancesAndLimitsTransformer.kt index d4f7d3db34..af365a24d7 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/SetBalancesAndLimitsTransformer.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/SetBalancesAndLimitsTransformer.kt @@ -3,6 +3,8 @@ package com.tangem.feature.wallet.presentation.wallet.state.transformers import arrow.core.Either import arrow.core.getOrElse import com.tangem.core.ui.extensions.stringReference +import com.tangem.core.ui.format.bigdecimal.crypto +import com.tangem.core.ui.format.bigdecimal.format import com.tangem.core.ui.utils.BigDecimalFormatter import com.tangem.domain.common.util.getCardsCount import com.tangem.domain.visa.model.VisaCurrency @@ -41,11 +43,9 @@ internal class SetBalancesAndLimitsTransformer( } private fun getContentBlockState(visaCurrency: VisaCurrency) = BalancesAndLimitsBlockState.Content( - availableBalance = BigDecimalFormatter.formatCryptoAmount( - visaCurrency.limits.remainingOtp, - visaCurrency.symbol, - visaCurrency.decimals, - ), + availableBalance = visaCurrency.limits.remainingOtp.format { + crypto(visaCurrency.symbol, visaCurrency.decimals) + }, limitDays = Days.daysBetween(DateTime.now(), visaCurrency.limits.expirationDate).days.inc(), isEnabled = true, onClick = clickIntents::onBalancesAndLimitsClick, @@ -72,11 +72,9 @@ internal class SetBalancesAndLimitsTransformer( imageResId = imageResId, onRenameClick = onRenameClick, onDeleteClick = onDeleteClick, - balance = BigDecimalFormatter.formatCryptoAmount( - visaCurrency.balances.available, - visaCurrency.symbol, - visaCurrency.decimals, - ), + balance = visaCurrency.balances.available.format { + crypto(visaCurrency.symbol, visaCurrency.decimals) + }, cardCount = userWallet.getCardsCount(), ) } diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/BalancesAndLimitsBottomSheetConverter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/BalancesAndLimitsBottomSheetConverter.kt index 146e196a65..dc965b29e2 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/BalancesAndLimitsBottomSheetConverter.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/BalancesAndLimitsBottomSheetConverter.kt @@ -1,6 +1,7 @@ package com.tangem.feature.wallet.presentation.wallet.state.transformers.converter -import com.tangem.core.ui.utils.BigDecimalFormatter +import com.tangem.core.ui.format.bigdecimal.crypto +import com.tangem.core.ui.format.bigdecimal.format import com.tangem.core.ui.utils.DateTimeFormatters import com.tangem.domain.visa.model.VisaCurrency import com.tangem.feature.wallet.presentation.wallet.state.model.BalancesAndLimitsBottomSheetConfig @@ -15,11 +16,7 @@ internal class BalancesAndLimitsBottomSheetConverter( ) : Converter { override fun convert(value: VisaCurrency): BalancesAndLimitsBottomSheetConfig { - fun formatAmount(amount: BigDecimal): String = BigDecimalFormatter.formatCryptoAmount( - amount, - cryptoCurrency = value.symbol, - decimals = value.decimals, - ) + fun formatAmount(amount: BigDecimal): String = amount.format { crypto(value.symbol, value.decimals) } val otpLimit = value.limits.remainingOtp.let(::formatAmount) val noOtpLimit = value.limits.remainingNoOtp.let(::formatAmount) diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/SingleWalletMarketPriceConverter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/SingleWalletMarketPriceConverter.kt index 8d710656dc..3611aa8119 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/SingleWalletMarketPriceConverter.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/SingleWalletMarketPriceConverter.kt @@ -4,6 +4,8 @@ import com.tangem.core.ui.components.marketprice.MarketPriceBlockState import com.tangem.core.ui.components.marketprice.PriceChangeState import com.tangem.core.ui.components.marketprice.PriceChangeType import com.tangem.core.ui.components.marketprice.utils.PriceChangeConverter +import com.tangem.core.ui.format.bigdecimal.format +import com.tangem.core.ui.format.bigdecimal.percent import com.tangem.core.ui.utils.BigDecimalFormatter import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.tokens.model.CryptoCurrencyStatus @@ -57,7 +59,7 @@ internal class SingleWalletMarketPriceConverter( private fun formatPriceChange(status: CryptoCurrencyStatus.Value): String { val priceChange = status.priceChange ?: return BigDecimalFormatter.EMPTY_BALANCE_SIGN - return BigDecimalFormatter.formatPercent(percent = priceChange, useAbsoluteValue = true) + return priceChange.format { percent() } } private fun getPriceChangeType(status: CryptoCurrencyStatus.Value): PriceChangeType { diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/TxHistoryItemStateConverter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/TxHistoryItemStateConverter.kt index 2d162a17c0..461f74999f 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/TxHistoryItemStateConverter.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/TxHistoryItemStateConverter.kt @@ -6,6 +6,8 @@ import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.extensions.resourceReference import com.tangem.core.ui.extensions.stringReference import com.tangem.core.ui.extensions.wrappedList +import com.tangem.core.ui.format.bigdecimal.crypto +import com.tangem.core.ui.format.bigdecimal.format import com.tangem.core.ui.utils.toTimeFormat import com.tangem.domain.txhistory.models.TxHistoryItem import com.tangem.domain.txhistory.models.TxHistoryItem.* @@ -15,7 +17,6 @@ import com.tangem.utils.StringsSigns.MINUS import com.tangem.utils.StringsSigns.PLUS import com.tangem.utils.converter.Converter import com.tangem.utils.toBriefAddressFormat -import com.tangem.utils.toFormattedCurrencyString internal class TxHistoryItemStateConverter( private val symbol: String, @@ -129,6 +130,6 @@ internal class TxHistoryItemStateConverter( this.amount.isZero() -> "" else -> if (isOutgoing) MINUS else PLUS } - return prefix + amount.toFormattedCurrencyString(currency = symbol, decimals = decimals) + return prefix + amount.format { crypto(symbol = symbol, decimals = decimals) } } } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/VisaTxDetailsBottomSheetConverter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/VisaTxDetailsBottomSheetConverter.kt index 6e8158da13..e655973c16 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/VisaTxDetailsBottomSheetConverter.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/VisaTxDetailsBottomSheetConverter.kt @@ -1,6 +1,8 @@ package com.tangem.feature.wallet.presentation.wallet.state.transformers.converter import com.tangem.core.ui.extensions.capitalize +import com.tangem.core.ui.format.bigdecimal.crypto +import com.tangem.core.ui.format.bigdecimal.format import com.tangem.core.ui.utils.BigDecimalFormatter import com.tangem.core.ui.utils.DateTimeFormatters import com.tangem.domain.visa.model.VisaCurrency @@ -66,11 +68,7 @@ internal class VisaTxDetailsBottomSheetConverter( } private fun formatNetworkAmount(amount: BigDecimal): String { - return BigDecimalFormatter.formatCryptoAmount( - cryptoAmount = amount, - cryptoCurrency = visaCurrency.symbol, - decimals = visaCurrency.decimals, - ) + return amount.format { crypto(visaCurrency.symbol, visaCurrency.decimals) } } private fun formatFiatAmount(amount: BigDecimal, fiatCurrency: Currency): String { diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/VisaTxHistoryItemStateConverter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/VisaTxHistoryItemStateConverter.kt index 60ac94068a..fe50fdf537 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/VisaTxHistoryItemStateConverter.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/VisaTxHistoryItemStateConverter.kt @@ -3,6 +3,8 @@ package com.tangem.feature.wallet.presentation.wallet.state.transformers.convert import com.tangem.core.ui.components.transactions.state.TransactionState import com.tangem.core.ui.extensions.capitalize import com.tangem.core.ui.extensions.stringReference +import com.tangem.core.ui.format.bigdecimal.crypto +import com.tangem.core.ui.format.bigdecimal.format import com.tangem.core.ui.utils.BigDecimalFormatter import com.tangem.core.ui.utils.DateTimeFormatters import com.tangem.domain.visa.model.VisaCurrency @@ -25,11 +27,7 @@ internal class VisaTxHistoryItemStateConverter( return TransactionState.Content( txHash = value.id, - amount = BigDecimalFormatter.formatCryptoAmount( - cryptoAmount = value.amount, - cryptoCurrency = visaCurrency.symbol, - decimals = visaCurrency.decimals, - ), + amount = value.amount.format { crypto(visaCurrency.symbol, visaCurrency.decimals) }, // Show tx fiat amount instead of tx time time = BigDecimalFormatter.formatFiatAmount( fiatAmount = value.fiatAmount, From d487f796beced2312460d6a8a8821fdd970509fa Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 18 Oct 2024 15:44:21 +0300 Subject: [PATCH 010/106] Updated on 2026-08-14 --- app/build.gradle.kts | 3 +++ app/src/main/res/resources.properties | 1 + 2 files changed, 4 insertions(+) create mode 100644 app/src/main/res/resources.properties diff --git a/app/build.gradle.kts b/app/build.gradle.kts index d9d1aaef00..b42a58fe8e 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -22,6 +22,9 @@ android { resources.excludes.add("META-INF/LICENSE.md") resources.excludes.add("META-INF/NOTICE.md") } + androidResources { + generateLocaleConfig = true + } } configurations.all { diff --git a/app/src/main/res/resources.properties b/app/src/main/res/resources.properties new file mode 100644 index 0000000000..d5a3ddc92a --- /dev/null +++ b/app/src/main/res/resources.properties @@ -0,0 +1 @@ +unqualifiedResLocale=en-US \ No newline at end of file From 7bd584c2619449384677de080ce9fba60bd14ea2 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 4 Oct 2024 10:57:07 +0400 Subject: [PATCH 011/106] Updated on 2026-08-14 --- .../network/DefaultNetworksStatusesStore.kt | 20 ++ .../local/network/NetworksStatusesStore.kt | 2 + .../data/staking/DefaultStakingRepository.kt | 55 ++--- .../repository/DefaultCurrenciesRepository.kt | 23 +-- .../repository/DefaultNetworksRepository.kt | 79 +++----- .../com/tangem/domain/core/lce/LceFlow.kt | 9 + .../com/tangem/domain/core/lce/LceRaise.kt | 8 + .../staking/repositories/StakingRepository.kt | 5 +- .../domain/tokens/model/NetworkStatus.kt | 5 + .../domain/tokens/FetchTokenListUseCase.kt | 86 +++++--- .../tokens/GetWalletTotalBalanceUseCase.kt | 5 +- .../CurrenciesStatusesLceOperations.kt | 191 ++++++++---------- .../operations/CurrencyStatusOperations.kt | 4 +- .../tokens/repository/CurrenciesRepository.kt | 2 +- .../repository/MockStakingRepository.kt | 13 +- .../domain/GetMultiWalletWarningsFactory.kt | 6 +- .../domain/MultiWalletTokenListStore.kt | 62 ++++++ .../implementors/MultiWalletContentLoader.kt | 6 +- .../MultiWalletContentLoaderFactory.kt | 6 +- .../subscribers/BasicTokenListSubscriber.kt | 4 +- .../MultiWalletTokenListSubscriber.kt | 11 +- .../SingleWalletWithTokenListSubscriber.kt | 8 +- .../wallet/viewmodels/WalletViewModel.kt | 8 +- .../viewmodels/WalletsUpdateActionResolver.kt | 4 +- .../intents/WalletCardClickIntents.kt | 4 + .../viewmodels/intents/WalletClickIntents.kt | 3 +- .../intents/WalletWarningsClickIntents.kt | 16 +- 27 files changed, 379 insertions(+), 266 deletions(-) create mode 100644 features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/MultiWalletTokenListStore.kt diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/network/DefaultNetworksStatusesStore.kt b/core/datasource/src/main/java/com/tangem/datasource/local/network/DefaultNetworksStatusesStore.kt index 88b241c3ff..e168276a5b 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/local/network/DefaultNetworksStatusesStore.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/local/network/DefaultNetworksStatusesStore.kt @@ -5,6 +5,7 @@ import com.tangem.datasource.local.datastore.core.StringKeyDataStoreDecorator import com.tangem.domain.tokens.model.NetworkStatus import com.tangem.domain.wallets.models.UserWalletId import com.tangem.utils.extensions.addOrReplace +import com.tangem.utils.extensions.replaceBy import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock @@ -27,4 +28,23 @@ internal class DefaultNetworksStatusesStore( store(key, newValues) } } + + override suspend fun storeAll(key: UserWalletId, values: Collection) { + mutex.withLock { + val currentValues = getSyncOrNull(key) ?: emptySet() + val updatedValues = currentValues.toMutableSet() + + values.forEach { newValue -> + val isReplaced = updatedValues.replaceBy(newValue) { + it.network == newValue.network + } + + if (!isReplaced) { + updatedValues.add(newValue) + } + } + + store(key, updatedValues) + } + } } \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/network/NetworksStatusesStore.kt b/core/datasource/src/main/java/com/tangem/datasource/local/network/NetworksStatusesStore.kt index a30a1b49ab..3ab684275f 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/local/network/NetworksStatusesStore.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/local/network/NetworksStatusesStore.kt @@ -11,4 +11,6 @@ interface NetworksStatusesStore { suspend fun getSyncOrNull(key: UserWalletId): Set? suspend fun store(key: UserWalletId, value: NetworkStatus) + + suspend fun storeAll(key: UserWalletId, values: Collection) } \ No newline at end of file diff --git a/data/staking/src/main/java/com/tangem/data/staking/DefaultStakingRepository.kt b/data/staking/src/main/java/com/tangem/data/staking/DefaultStakingRepository.kt index 71bd21a01e..8633e9d88c 100644 --- a/data/staking/src/main/java/com/tangem/data/staking/DefaultStakingRepository.kt +++ b/data/staking/src/main/java/com/tangem/data/staking/DefaultStakingRepository.kt @@ -2,7 +2,6 @@ package com.tangem.data.staking import android.util.Base64 import arrow.core.getOrElse -import arrow.core.raise.catch import com.squareup.moshi.Moshi import com.tangem.blockchain.common.Amount import com.tangem.blockchain.common.Blockchain @@ -30,8 +29,6 @@ import com.tangem.datasource.api.stakekit.models.response.model.YieldBalanceWrap import com.tangem.datasource.api.stakekit.models.response.model.transaction.tron.TronStakeKitTransaction import com.tangem.datasource.local.token.StakingBalanceStore import com.tangem.datasource.local.token.StakingYieldsStore -import com.tangem.domain.core.lce.LceFlow -import com.tangem.domain.core.lce.lceFlow import com.tangem.domain.staking.model.StakingApproval import com.tangem.domain.staking.model.StakingAvailability import com.tangem.domain.staking.model.StakingEntryInfo @@ -57,6 +54,7 @@ import com.tangem.utils.coroutines.CoroutineDispatcherProvider import com.tangem.utils.extensions.orZero import kotlinx.coroutines.flow.* import kotlinx.coroutines.launch +import kotlinx.coroutines.plus import kotlinx.coroutines.withContext import timber.log.Timber @@ -385,15 +383,22 @@ internal class DefaultStakingRepository( refresh: Boolean, ) = withContext(dispatchers.io) { if (!stakingFeatureToggle.isStakingEnabled) return@withContext + try { - isYieldBalanceFetching.update { - it + (userWalletId to true) - } cacheRegistry.invokeOnExpire( key = getYieldBalancesKey(userWalletId), skipCache = refresh, block = { - val yields = getEnabledYields() + isYieldBalanceFetching.update { + it + (userWalletId to true) + } + + val yields = getEnabledYields().ifEmpty { + Timber.i("No enabled yields for $userWalletId") + stakingBalanceStore.store(userWalletId, emptySet()) + + return@invokeOnExpire + } val availableCurrencies = cryptoCurrencies .mapNotNull { currency -> val addresses = walletManagersFacade.getAddresses(userWalletId, currency.network) @@ -410,10 +415,15 @@ internal class DefaultStakingRepository( } .map { getBalanceRequestData(it.first.value, it.second) } .ifEmpty { - cacheRegistry.invalidate(getYieldBalancesKey(userWalletId)) - error("No addresses found") + Timber.i("No yield balances available for $userWalletId") + stakingBalanceStore.store(userWalletId, emptySet()) + + return@invokeOnExpire } - val result = stakeKitApi.getMultipleYieldBalances(availableCurrencies).getOrThrow() + + val result = stakeKitApi + .getMultipleYieldBalances(availableCurrencies) + .getOrThrow() stakingBalanceStore.store(userWalletId, result) }, @@ -446,27 +456,22 @@ internal class DefaultStakingRepository( } }.cancellable() - override fun getMultiYieldBalanceLce( + override fun getMultiYieldBalance( userWalletId: UserWalletId, cryptoCurrencies: List, - ): LceFlow = lceFlow { + ): Flow = channelFlow { if (!stakingFeatureToggle.isStakingEnabled) { send(YieldBalanceList.Empty) } else { - launch(dispatchers.io) { - combine( - stakingBalanceStore.get(userWalletId), - isYieldBalanceFetching.map { it.getOrElse(userWalletId) { false } }, - ) { result, isFetching -> - val balances = yieldBalanceListConverter.convert(result) - send(balances, isStillLoading = isFetching) - }.collect() - } + stakingBalanceStore.get(userWalletId) + .onEach { + val balances = yieldBalanceListConverter.convert(it) + send(balances) + } + .launchIn(scope = this + dispatchers.io) + withContext(dispatchers.io) { - catch( - block = { fetchMultiYieldBalance(userWalletId, cryptoCurrencies, refresh = false) }, - catch = { raise(it) }, - ) + fetchMultiYieldBalance(userWalletId, cryptoCurrencies, refresh = false) } } } diff --git a/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultCurrenciesRepository.kt b/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultCurrenciesRepository.kt index a3b8911411..4664e120ce 100644 --- a/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultCurrenciesRepository.kt +++ b/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultCurrenciesRepository.kt @@ -42,6 +42,7 @@ import com.tangem.utils.coroutines.CoroutineDispatcherProvider import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.* import kotlinx.coroutines.launch +import kotlinx.coroutines.plus import kotlinx.coroutines.withContext import timber.log.Timber import com.tangem.blockchain.common.FeePaidCurrency as FeePaidSdkCurrency @@ -206,18 +207,14 @@ internal class DefaultCurrenciesRepository( } } - override fun getWalletCurrenciesUpdates(userWalletId: UserWalletId): LceFlow> { - return lceFlow { - val userWallet = catch({ getUserWallet(userWalletId) }) { - raise(it) - } + override fun getWalletCurrenciesUpdates(userWalletId: UserWalletId): Flow> { + return channelFlow { + val userWallet = getUserWallet(userWalletId) if (userWallet.isMultiCurrency) { - getMultiCurrencyWalletCurrenciesUpdatesLce(userWalletId).collect(::send) + getMultiCurrencyWalletCurrenciesUpdates(userWalletId).collect(::send) } else { - val currency = catch({ getSingleCurrencyWalletPrimaryCurrency(userWalletId) }) { - raise(it) - } + val currency = getSingleCurrencyWalletPrimaryCurrency(userWalletId) send(listOf(currency)) } } @@ -260,16 +257,14 @@ internal class DefaultCurrenciesRepository( val userWallet = getUserWallet(userWalletId) ensureIsCorrectUserWallet(userWallet, isMultiCurrencyWalletExpected = true) - launch(dispatchers.io) { - getMultiCurrencyWalletCurrencies(userWallet) - .collectLatest(::send) - } + getMultiCurrencyWalletCurrencies(userWallet) + .onEach { send(it) } + .launchIn(scope = this + dispatchers.io) withContext(dispatchers.io) { fetchTokensIfCacheExpired(userWallet, refresh = false) } } - .cancellable() } override fun getMultiCurrencyWalletCurrenciesUpdatesLce( diff --git a/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultNetworksRepository.kt b/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultNetworksRepository.kt index 3762b4ddee..d61f12d1a3 100644 --- a/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultNetworksRepository.kt +++ b/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultNetworksRepository.kt @@ -54,29 +54,25 @@ internal class DefaultNetworksRepository( userWalletId: UserWalletId, networks: Set, ): Flow> = channelFlow { - launch(dispatchers.io) { - networksStatusesStore.get(userWalletId) - .collectLatest(::send) - } + networksStatusesStore.get(userWalletId) + .onEach(::send) + .launchIn(scope = this + dispatchers.io) withContext(dispatchers.io) { - fetchNetworksStatusesIfCacheExpired(userWalletId, networks, false) + fetchNetworksStatusesIfCacheExpired(userWalletId, networks, refresh = false) } } - .cancellable() override fun getNetworkStatusesUpdatesLce( userWalletId: UserWalletId, networks: Set, ): LceFlow> = lceFlow { - launch(dispatchers.io) { - combine( - networksStatusesStore.get(userWalletId), - isNetworkStatusesFetching.map { it.getOrElse(userWalletId) { false } }, - ) { statuses, isFetching -> - send(statuses, isStillLoading = isFetching) - }.collect() - } + combine( + networksStatusesStore.get(userWalletId), + isNetworkStatusesFetching.map { it.getOrElse(userWalletId) { false } }, + ) { statuses, isFetching -> + send(statuses, isStillLoading = isFetching || networks.size != statuses.size) + }.launchIn(scope = this + dispatchers.io) withContext(dispatchers.io) { catch({ fetchNetworksStatusesIfCacheExpired(userWalletId, networks, refresh = false) }) { @@ -185,25 +181,32 @@ internal class DefaultNetworksRepository( userWalletId: UserWalletId, networks: Set, refresh: Boolean, - ) { - val currencies = getCurrencies(userWalletId, networks) - val networksDeferred = networks.mapNotNull { network -> - fetchNetworkStatusIfCacheExpired(userWalletId, network, currencies, refresh) + ) = coroutineScope { + if (refresh) { + val statusesToRefresh = networks.map { NetworkStatus(it, NetworkStatus.Loading) } + networksStatusesStore.storeAll(userWalletId, statusesToRefresh) } - if (networksDeferred.isNotEmpty()) { - try { - isNetworkStatusesFetching.update { - it + (userWalletId to true) - } + val currencies = getCurrencies(userWalletId, networks) + val networksDeferred = networks.mapNotNull { network -> + coroutineScope { + val key = getNetworksStatusesCacheKey(userWalletId, network) - networksDeferred.awaitAll() - } finally { - isNetworkStatusesFetching.update { - it - userWalletId + if (refresh || cacheRegistry.isExpired(key)) { + async { + cacheRegistry.invokeOnExpire( + key = key, + skipCache = refresh, + block = { fetchNetworkStatus(userWalletId, network, currencies) }, + ) + } + } else { + null } } } + + networksDeferred.awaitAll() } private suspend fun fetchNetworksPendingTransactions( @@ -222,31 +225,13 @@ internal class DefaultNetworksRepository( } } - private suspend fun fetchNetworkStatusIfCacheExpired( - userWalletId: UserWalletId, - network: Network, - currencies: Sequence, - refresh: Boolean, - ): Deferred? = coroutineScope { - val key = getNetworksStatusesCacheKey(userWalletId, network) - if (refresh || cacheRegistry.isExpired(key)) { - async { - cacheRegistry.invokeOnExpire( - key = key, - skipCache = refresh, - block = { fetchNetworkStatus(userWalletId, network, currencies) }, - ) - } - } else { - null - } - } - private suspend fun fetchNetworkStatus( userWalletId: UserWalletId, network: Network, currencies: Sequence, ) { + networksStatusesStore.store(userWalletId, NetworkStatus(network, NetworkStatus.Loading)) + val result = walletManagersFacade.update( userWalletId = userWalletId, network = network, diff --git a/domain/core/src/main/kotlin/com/tangem/domain/core/lce/LceFlow.kt b/domain/core/src/main/kotlin/com/tangem/domain/core/lce/LceFlow.kt index 6204c49430..037775753e 100644 --- a/domain/core/src/main/kotlin/com/tangem/domain/core/lce/LceFlow.kt +++ b/domain/core/src/main/kotlin/com/tangem/domain/core/lce/LceFlow.kt @@ -1,5 +1,6 @@ package com.tangem.domain.core.lce +import arrow.atomic.AtomicBoolean import arrow.core.raise.Raise import com.tangem.domain.core.utils.lceContent import com.tangem.domain.core.utils.lceError @@ -34,6 +35,8 @@ class LceFlowScope @PublishedApi internal constructor( private val ifLoading: suspend LceFlowScope.(C?) -> Unit, ) : Raise, CoroutineScope by producerScope { + val isLoading: AtomicBoolean = AtomicBoolean(value = true) + /** * Sends a error of type [E] within the [ProducerScope] and then closes it for send. * All subsequent sends will be ignored. @@ -46,6 +49,8 @@ class LceFlowScope @PublishedApi internal constructor( * @param r Error to raise. */ override fun raise(r: E): Nothing { + isLoading.set(false) + producerScope.trySendBlocking(r.lceError()) producerScope.close() @@ -66,6 +71,8 @@ class LceFlowScope @PublishedApi internal constructor( * @param isStillLoading A flag indicating whether the content is still loading. */ suspend fun send(content: C, isStillLoading: Boolean = false) { + isLoading.set(isStillLoading) + val value = if (isStillLoading) { ifLoading(content) return @@ -89,6 +96,8 @@ class LceFlowScope @PublishedApi internal constructor( suspend fun send(value: Lce) { if (producerScope.isClosedForSend) return + isLoading.set(value.isLoading()) + producerScope.send(value) } } diff --git a/domain/core/src/main/kotlin/com/tangem/domain/core/lce/LceRaise.kt b/domain/core/src/main/kotlin/com/tangem/domain/core/lce/LceRaise.kt index 1484205426..dce3342eb3 100644 --- a/domain/core/src/main/kotlin/com/tangem/domain/core/lce/LceRaise.kt +++ b/domain/core/src/main/kotlin/com/tangem/domain/core/lce/LceRaise.kt @@ -1,6 +1,8 @@ package com.tangem.domain.core.lce import arrow.atomic.Atomic +import arrow.core.Either +import arrow.core.identity import arrow.core.raise.Raise import arrow.core.raise.RaiseDSL import arrow.core.raise.recover @@ -97,6 +99,12 @@ class LceRaise @PublishedApi internal constructor( is Lce.Content -> content is Lce.Error -> raise(r = this) } + + @RaiseDSL + fun Either.bindEither(): C = fold( + ifLeft = { raise(it) }, + ifRight = ::identity, + ) } /** diff --git a/domain/staking/src/main/java/com/tangem/domain/staking/repositories/StakingRepository.kt b/domain/staking/src/main/java/com/tangem/domain/staking/repositories/StakingRepository.kt index 6f4ba8fd32..6e17391a27 100644 --- a/domain/staking/src/main/java/com/tangem/domain/staking/repositories/StakingRepository.kt +++ b/domain/staking/src/main/java/com/tangem/domain/staking/repositories/StakingRepository.kt @@ -3,7 +3,6 @@ package com.tangem.domain.staking.repositories import com.tangem.blockchain.common.Amount import com.tangem.blockchain.common.TransactionData import com.tangem.blockchain.common.transaction.Fee -import com.tangem.domain.core.lce.LceFlow import com.tangem.domain.staking.model.StakingApproval import com.tangem.domain.staking.model.StakingAvailability import com.tangem.domain.staking.model.StakingEntryInfo @@ -55,10 +54,10 @@ interface StakingRepository { cryptoCurrencies: List, ): Flow - fun getMultiYieldBalanceLce( + fun getMultiYieldBalance( userWalletId: UserWalletId, cryptoCurrencies: List, - ): LceFlow + ): Flow suspend fun getMultiYieldBalanceSync( userWalletId: UserWalletId, diff --git a/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/NetworkStatus.kt b/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/NetworkStatus.kt index 08a746b12e..d33b011230 100644 --- a/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/NetworkStatus.kt +++ b/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/NetworkStatus.kt @@ -21,6 +21,11 @@ data class NetworkStatus( */ sealed class Value + /** + * Represents the state where the network is refreshing. + */ + data object Refreshing : Value() + /** * Represents the state where the network is unreachable. * diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/FetchTokenListUseCase.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/FetchTokenListUseCase.kt index 394cd7c1dc..f1ef16bca8 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/FetchTokenListUseCase.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/FetchTokenListUseCase.kt @@ -40,38 +40,39 @@ class FetchTokenListUseCase( * network statuses, and quotes for associated tokens. * * @param userWalletId The ID of the user's wallet. - * @param refresh Indicates whether to force a refresh of the token list data. + * @param mode The refresh mode to control the fetching process. * @return An [Either] representing success (Right) or an error (Left) in fetching the token list. */ - suspend operator fun invoke(userWalletId: UserWalletId, refresh: Boolean = false): Either { - return either { - val currencies = fetchCurrencies(userWalletId, refresh) + suspend operator fun invoke( + userWalletId: UserWalletId, + mode: RefreshMode = RefreshMode.NONE, + ): Either = either { + val currencies = fetchCurrencies(userWalletId, refresh = mode.refreshCurrencies) - coroutineScope { - val fetchStatuses = async { - fetchNetworksStatuses( - userWalletId, - currencies.mapTo(hashSetOf()) { it.network }, - refresh, - ) - } - val fetchQuotes = async { - fetchQuotes( - currencies.mapTo(hashSetOf()) { it.id }, - refresh, - ) - } - - val yieldBalances = async { - fetchYieldBalances( - userWalletId = userWalletId, - currencies = currencies, - refresh = refresh, - ) - } - - awaitAll(fetchStatuses, fetchQuotes, yieldBalances) + coroutineScope { + val fetchStatuses = async { + fetchNetworksStatuses( + userWalletId, + currencies.mapTo(hashSetOf()) { it.network }, + refresh = mode.refreshNetworksStatuses, + ) } + val fetchQuotes = async { + fetchQuotes( + currencies.mapTo(hashSetOf()) { it.id }, + refresh = mode.refreshQuotes, + ) + } + + val yieldBalances = async { + fetchYieldBalances( + userWalletId = userWalletId, + currencies = currencies, + refresh = mode.refreshYieldBalances, + ) + } + + awaitAll(fetchStatuses, fetchQuotes, yieldBalances) } } @@ -120,4 +121,33 @@ class FetchTokenListUseCase( catch = { /* Ignore error */ }, ) } + + /** + * Represents the refresh modes available for fetching token list information. + */ + enum class RefreshMode( + internal val refreshCurrencies: Boolean, + internal val refreshNetworksStatuses: Boolean, + internal val refreshQuotes: Boolean, + internal val refreshYieldBalances: Boolean, + ) { + NONE( + refreshCurrencies = false, + refreshNetworksStatuses = false, + refreshQuotes = false, + refreshYieldBalances = false, + ), + FULL( + refreshCurrencies = true, + refreshNetworksStatuses = true, + refreshQuotes = true, + refreshYieldBalances = true, + ), + SKIP_CURRENCIES( + refreshCurrencies = false, + refreshNetworksStatuses = true, + refreshQuotes = true, + refreshYieldBalances = true, + ), + } } \ No newline at end of file diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetWalletTotalBalanceUseCase.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetWalletTotalBalanceUseCase.kt index 012dd2847a..ebf6a72cd7 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetWalletTotalBalanceUseCase.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetWalletTotalBalanceUseCase.kt @@ -85,9 +85,6 @@ class GetWalletTotalBalanceUseCase( stakingRepository = stakingRepository, ) - return operations.getCurrenciesStatuses( - userWalletId = userWalletId, - isSingleCurrencyWalletsAllowed = true, - ) + return operations.getCurrenciesStatuses(userWalletId) } } \ No newline at end of file diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/CurrenciesStatusesLceOperations.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/CurrenciesStatusesLceOperations.kt index 419842f017..730dbc541b 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/CurrenciesStatusesLceOperations.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/CurrenciesStatusesLceOperations.kt @@ -1,12 +1,13 @@ package com.tangem.domain.tokens.operations import arrow.core.* +import arrow.core.raise.ensureNotNull import arrow.core.raise.recover import com.tangem.domain.core.lce.Lce import com.tangem.domain.core.lce.LceFlow import com.tangem.domain.core.lce.lce -import com.tangem.domain.core.utils.lceError -import com.tangem.domain.core.utils.lceLoading +import com.tangem.domain.core.lce.lceFlow +import com.tangem.domain.core.utils.EitherFlow import com.tangem.domain.staking.model.stakekit.YieldBalance import com.tangem.domain.staking.model.stakekit.YieldBalanceList import com.tangem.domain.staking.repositories.StakingRepository @@ -26,147 +27,126 @@ internal class CurrenciesStatusesLceOperations( private val stakingRepository: StakingRepository, ) { - fun getCurrenciesStatuses( - userWalletId: UserWalletId, - isSingleCurrencyWalletsAllowed: Boolean = false, - ): LceFlow> { + fun getCurrenciesStatuses(userWalletId: UserWalletId): LceFlow> { return transformToCurrenciesStatuses( userWalletId = userWalletId, - flow = if (isSingleCurrencyWalletsAllowed) { - getWalletCurrencies(userWalletId) - } else { - getMultiCurrencyWalletCurrencies(userWalletId) - }, + currenciesFlow = getWalletCurrencies(userWalletId), ) } @OptIn(ExperimentalCoroutinesApi::class) private fun transformToCurrenciesStatuses( userWalletId: UserWalletId, - flow: LceFlow>, - ): LceFlow> { - return flow.transformLatest transform@{ maybeCurrencies -> - val nonEmptyCurrencies = maybeCurrencies.fold( - ifLoading = { maybeContent -> - emit(createLoadingCurrenciesStatuses(maybeContent)) - return@transform - }, - ifContent = { content -> - val nonEmptyCurrencies = content.toNonEmptyListOrNull() + currenciesFlow: EitherFlow>, + ): LceFlow> = lceFlow { + currenciesFlow.collectLatest { maybeCurrencies -> + val nonEmptyCurrencies = maybeCurrencies.bind().toNonEmptyListOrNull() + ensureNotNull(nonEmptyCurrencies) { TokenListError.EmptyTokens } - if (nonEmptyCurrencies == null) { - emit(TokenListError.EmptyTokens.lceError()) - return@transform - } else { - nonEmptyCurrencies - } - }, - ifError = { error -> - emit(error.lceError()) - return@transform - }, - ) + // This is only 'true' when the flow here is empty, such as during initial loading + if (isLoading.get()) { + val loadingCurrencies = createCurrenciesStatuses( + currencies = nonEmptyCurrencies, + maybeNetworkStatuses = null, + maybeQuotes = null, + maybeYieldBalances = null, + + ) + send(loadingCurrencies) + } val (networks, currenciesIds) = getIds(nonEmptyCurrencies) + fun createCurrenciesStatuses( + maybeQuotes: Either>?, + maybeNetworkStatuses: Either>?, + maybeYieldBalances: Either?, + ): Lce> = createCurrenciesStatuses( + currencies = nonEmptyCurrencies, + maybeQuotes = maybeQuotes, + maybeNetworkStatuses = maybeNetworkStatuses, + maybeYieldBalances = maybeYieldBalances, + ) + combine( getQuotes(currenciesIds), getNetworksStatuses(userWalletId, networks), getYieldBalances(userWalletId, nonEmptyCurrencies), - - ) { maybeQuotes, maybeNetworksStatuses, maybeYieldBalances -> - val statuses = createCurrenciesStatuses( - currencies = nonEmptyCurrencies, - maybeQuotes = maybeQuotes, - maybeNetworkStatuses = maybeNetworksStatuses, - maybeYieldBalances = maybeYieldBalances, - ) - emit(statuses) - }.collect() - } - } - - private fun createLoadingCurrenciesStatuses( - maybeCurrencies: List?, - ): Lce> { - val nonEmptyCurrencies = maybeCurrencies?.toNonEmptyListOrNull() - - val statuses = if (nonEmptyCurrencies == null) { - lceLoading() - } else { - createCurrenciesStatuses( - currencies = nonEmptyCurrencies, - maybeNetworkStatuses = null, - maybeQuotes = null, - maybeYieldBalances = null, + ::createCurrenciesStatuses, ) + .distinctUntilChanged() + .mapLatest { maybeCurrenciesStatuses -> + send(maybeCurrenciesStatuses) + } + .launchIn(scope = this) } - - return statuses } - private fun getWalletCurrencies(userWalletId: UserWalletId): LceFlow> { + private fun getWalletCurrencies(userWalletId: UserWalletId): EitherFlow> { return currenciesRepository.getWalletCurrenciesUpdates(userWalletId) - .map { maybeCurrencies -> - maybeCurrencies.mapError { TokenListError.DataError(it) } - } - } - - private fun getMultiCurrencyWalletCurrencies( - userWalletId: UserWalletId, - ): LceFlow> { - return currenciesRepository.getMultiCurrencyWalletCurrenciesUpdatesLce(userWalletId) + .map, Either>> { it.right() } + .catch { emit(TokenListError.DataError(it).left()) } .distinctUntilChanged() - .map { maybeCurrencies -> - maybeCurrencies.mapError { TokenListError.DataError(it) } - } } private fun createCurrenciesStatuses( currencies: NonEmptyList, maybeQuotes: Either>?, - maybeNetworkStatuses: Lce>?, - maybeYieldBalances: Lce?, + maybeNetworkStatuses: Either>?, + maybeYieldBalances: Either?, ): Lce> = lce { - isLoading.set(maybeNetworkStatuses == null) + isLoading.set(maybeNetworkStatuses == null || maybeYieldBalances == null) var quotesRetrievingFailed = false - val networksStatuses = maybeNetworkStatuses?.bindOrNull()?.toNonEmptySetOrNull() + val networksStatuses = maybeNetworkStatuses?.bindEither()?.toNonEmptySetOrNull() + val yieldBalances = maybeYieldBalances?.bindEither() val quotes = recover({ maybeQuotes?.bind()?.toNonEmptySetOrNull() }) { - quotesRetrievingFailed = true - null - }?.ifEmpty { - quotesRetrievingFailed = true null } - val yieldBalances = maybeYieldBalances?.getOrNull() + if (quotes == null) { + quotesRetrievingFailed = true + } currencies.map { currency -> val quote = quotes?.firstOrNull { it.rawCurrencyId == currency.id.rawCurrencyId } val networkStatus = networksStatuses?.firstOrNull { it.network == currency.network } - val address = extractAddress(networkStatus) - val supportedIntegration = stakingRepository.getSupportedIntegrationId(currency.id) - val yieldBalance = if (supportedIntegration.isNullOrBlank().not()) { - (yieldBalances as? YieldBalanceList.Data)?.getBalance( - address = address, - integrationId = supportedIntegration, - ) - } else { - null - } + val yieldBalance = findYieldBalanceOrNull(yieldBalances, currency, networkStatus) - createCurrencyStatus( + val currencyStatus = createCurrencyStatus( currency = currency, quote = quote, networkStatus = networkStatus, yieldBalance = yieldBalance, ignoreQuote = quotesRetrievingFailed, ) + + if (currencyStatus.value is CryptoCurrencyStatus.Loading) { + isLoading.set(true) + } + + currencyStatus } } + private fun findYieldBalanceOrNull( + yieldBalances: YieldBalanceList?, + currency: CryptoCurrency, + networkStatus: NetworkStatus?, + ): YieldBalance? { + if (yieldBalances !is YieldBalanceList.Data) return null + + val supportedIntegration = stakingRepository.getSupportedIntegrationId(currency.id) + + if (supportedIntegration.isNullOrBlank()) return null + + return yieldBalances.getBalance( + address = extractAddress(networkStatus), + integrationId = supportedIntegration, + ) + } + private fun createCurrencyStatus( currency: CryptoCurrency, quote: Quote?, @@ -189,28 +169,27 @@ internal class CurrenciesStatusesLceOperations( return quotesRepository.getQuotesUpdates(tokensIds) .map, Either>> { it.right() } .catch { emit(TokenListError.DataError(it).left()) } + .distinctUntilChanged() } private fun getNetworksStatuses( userWalletId: UserWalletId, networks: NonEmptySet, - ): LceFlow> { - return networksRepository.getNetworkStatusesUpdatesLce(userWalletId, networks) - .map { maybeStatuses -> - maybeStatuses.mapError { TokenListError.DataError(it) } - } + ): EitherFlow> { + return networksRepository.getNetworkStatusesUpdates(userWalletId, networks) + .map, Either>> { it.right() } + .catch { emit(TokenListError.DataError(it).left()) } + .distinctUntilChanged() } private fun getYieldBalances( userWalletId: UserWalletId, cryptoCurrencies: List, - ): LceFlow { - return stakingRepository.getMultiYieldBalanceLce( - userWalletId = userWalletId, - cryptoCurrencies = cryptoCurrencies, - ).map { maybeBalances -> - maybeBalances.mapError { TokenListError.DataError(it) } - } + ): EitherFlow { + return stakingRepository.getMultiYieldBalance(userWalletId, cryptoCurrencies) + .map> { it.right() } + .catch { emit(TokenListError.DataError(it).left()) } + .distinctUntilChanged() } private fun getIds(currencies: List): Pair, NonEmptySet> { diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/CurrencyStatusOperations.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/CurrencyStatusOperations.kt index 6cbaf22b37..ff31ccc64a 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/CurrencyStatusOperations.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/CurrencyStatusOperations.kt @@ -16,7 +16,9 @@ internal class CurrencyStatusOperations( private fun createStatus(): CryptoCurrencyStatus.Value { return when (val status = networkStatus?.value) { - null -> CryptoCurrencyStatus.Loading + null, + is NetworkStatus.Refreshing, + -> CryptoCurrencyStatus.Loading is NetworkStatus.MissedDerivation -> createMissedDerivationStatus() is NetworkStatus.Unreachable -> createUnreachableStatus(status) is NetworkStatus.NoAccount -> createNoAccountStatus(status) diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/CurrenciesRepository.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/CurrenciesRepository.kt index d8c8c7cf37..8a19b705e9 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/CurrenciesRepository.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/CurrenciesRepository.kt @@ -81,7 +81,7 @@ interface CurrenciesRepository { * @param userWalletId The unique identifier of the user wallet. * @return A list of [CryptoCurrency]. */ - fun getWalletCurrenciesUpdates(userWalletId: UserWalletId): LceFlow> + fun getWalletCurrenciesUpdates(userWalletId: UserWalletId): Flow> /** * Retrieves the primary cryptocurrency for a specific single-currency user wallet. diff --git a/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/repository/MockStakingRepository.kt b/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/repository/MockStakingRepository.kt index 32838c0406..9b702bacbb 100644 --- a/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/repository/MockStakingRepository.kt +++ b/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/repository/MockStakingRepository.kt @@ -4,8 +4,6 @@ import com.tangem.blockchain.common.Amount import com.tangem.blockchain.common.TransactionData import com.tangem.blockchain.common.TransactionStatus import com.tangem.blockchain.common.transaction.Fee -import com.tangem.domain.core.lce.LceFlow -import com.tangem.domain.core.lce.lceFlow import com.tangem.domain.staking.model.StakingApproval import com.tangem.domain.staking.model.StakingAvailability import com.tangem.domain.staking.model.StakingEntryInfo @@ -20,6 +18,7 @@ import com.tangem.domain.tokens.model.Network import com.tangem.domain.wallets.models.UserWalletId import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.channelFlow +import kotlinx.coroutines.flow.flowOf import org.joda.time.DateTime import java.math.BigDecimal @@ -157,16 +156,10 @@ class MockStakingRepository : StakingRepository { ) } - override fun getMultiYieldBalanceLce( + override fun getMultiYieldBalance( userWalletId: UserWalletId, cryptoCurrencies: List, - ): LceFlow = lceFlow { - send( - YieldBalanceList.Data( - balances = listOf(YieldBalance.Error), - ), - ) - } + ): Flow = flowOf() override suspend fun getMultiYieldBalanceSync( userWalletId: UserWalletId, diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/GetMultiWalletWarningsFactory.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/GetMultiWalletWarningsFactory.kt index 5ea90e12d4..90b969933f 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/GetMultiWalletWarningsFactory.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/GetMultiWalletWarningsFactory.kt @@ -7,7 +7,6 @@ import com.tangem.domain.demo.IsDemoCardUseCase import com.tangem.domain.promo.PromoBanner import com.tangem.domain.settings.IsReadyToShowRateAppUseCase import com.tangem.domain.settings.ShouldShowRingPromoUseCase -import com.tangem.domain.tokens.GetTokenListUseCase import com.tangem.domain.tokens.error.TokenListError import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.tokens.model.CryptoCurrencyStatus @@ -30,7 +29,7 @@ import kotlin.collections.count @Suppress("LongParameterList") @ViewModelScoped internal class GetMultiWalletWarningsFactory @Inject constructor( - private val getTokenListUseCase: GetTokenListUseCase, + private val tokenListStore: MultiWalletTokenListStore, private val isDemoCardUseCase: IsDemoCardUseCase, private val isReadyToShowRateAppUseCase: IsReadyToShowRateAppUseCase, private val shouldShowRingPromoUseCase: ShouldShowRingPromoUseCase, @@ -44,7 +43,7 @@ internal class GetMultiWalletWarningsFactory @Inject constructor( val promoFlow = flow { emit(promoRepository.getRingPromoBanner()) } return combine( - flow = getTokenListUseCase.launch(userWallet.walletId), + flow = tokenListStore.getOrThrow(userWallet.walletId), flow2 = isReadyToShowRateAppUseCase(), flow3 = isNeedToBackupUseCase(userWallet.walletId), flow4 = shouldShowRingPromoUseCase(userWalletId = userWallet.walletId), @@ -129,6 +128,7 @@ internal class GetMultiWalletWarningsFactory @Inject constructor( clickIntents: WalletClickIntents, ) { val currencies = maybeTokenList.getMissingAddressCurrencies() + .ifEmpty { return } addIf( element = WalletNotification.Informational.MissingAddresses( diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/MultiWalletTokenListStore.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/MultiWalletTokenListStore.kt new file mode 100644 index 0000000000..d25f60ca3e --- /dev/null +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/MultiWalletTokenListStore.kt @@ -0,0 +1,62 @@ +package com.tangem.feature.wallet.presentation.wallet.domain + +import com.tangem.domain.core.lce.LceFlow +import com.tangem.domain.tokens.GetTokenListUseCase +import com.tangem.domain.tokens.error.TokenListError +import com.tangem.domain.tokens.model.TokenList +import com.tangem.domain.wallets.models.UserWalletId +import dagger.hilt.android.scopes.ViewModelScoped +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.ensureActive +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.shareIn +import timber.log.Timber +import java.util.concurrent.ConcurrentHashMap +import javax.inject.Inject + +@ViewModelScoped +internal class MultiWalletTokenListStore @Inject constructor( + private val getTokenListUseCase: GetTokenListUseCase, +) { + + private val flows: ConcurrentHashMap> by lazy { + ConcurrentHashMap() + } + + fun addIfNot(userWalletId: UserWalletId, coroutineScope: CoroutineScope) { + if (flows[userWalletId] != null) { + Timber.d("Flow with token list for $userWalletId already exists") + return + } + + coroutineScope.ensureActive() + + flows[userWalletId] = getTokenListUseCase + .launch(userWalletId) + .shareIn( + scope = coroutineScope, + started = SharingStarted.WhileSubscribed(), + replay = 1, + ) + + Timber.d("Flow with token list for $userWalletId created") + } + + fun getOrThrow(userWalletId: UserWalletId): LceFlow { + return requireNotNull(flows[userWalletId]) { + "Flow with token list for $userWalletId doesn't exist" + } + } + + fun remove(userWalletId: UserWalletId) { + flows.remove(userWalletId) + + Timber.d("Flow with token list for $userWalletId removed") + } + + fun clear() { + flows.clear() + + Timber.d("All flows with token list cleared") + } +} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/MultiWalletContentLoader.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/MultiWalletContentLoader.kt index 7dc59658c4..ee5cdac9f1 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/MultiWalletContentLoader.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/MultiWalletContentLoader.kt @@ -2,12 +2,12 @@ package com.tangem.feature.wallet.presentation.wallet.loaders.implementors import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase import com.tangem.domain.tokens.ApplyTokenListSortingUseCase -import com.tangem.domain.tokens.GetTokenListUseCase import com.tangem.domain.tokens.RunPolkadotAccountHealthCheckUseCase import com.tangem.domain.wallets.models.UserWallet import com.tangem.feature.wallet.presentation.wallet.analytics.utils.TokenListAnalyticsSender import com.tangem.feature.wallet.presentation.wallet.analytics.utils.WalletWarningsAnalyticsSender import com.tangem.feature.wallet.presentation.wallet.domain.GetMultiWalletWarningsFactory +import com.tangem.feature.wallet.presentation.wallet.domain.MultiWalletTokenListStore import com.tangem.feature.wallet.presentation.wallet.domain.WalletWithFundsChecker import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController import com.tangem.feature.wallet.presentation.wallet.subscribers.MultiWalletTokenListSubscriber @@ -23,7 +23,7 @@ internal class MultiWalletContentLoader( private val tokenListAnalyticsSender: TokenListAnalyticsSender, private val walletWarningsAnalyticsSender: WalletWarningsAnalyticsSender, private val walletWithFundsChecker: WalletWithFundsChecker, - private val getTokenListUseCase: GetTokenListUseCase, + private val tokenListStore: MultiWalletTokenListStore, private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase, private val applyTokenListSortingUseCase: ApplyTokenListSortingUseCase, private val getMultiWalletWarningsFactory: GetMultiWalletWarningsFactory, @@ -38,7 +38,7 @@ internal class MultiWalletContentLoader( clickIntents = clickIntents, tokenListAnalyticsSender = tokenListAnalyticsSender, walletWithFundsChecker = walletWithFundsChecker, - getTokenListUseCase = getTokenListUseCase, + tokenListStore = tokenListStore, getSelectedAppCurrencyUseCase = getSelectedAppCurrencyUseCase, applyTokenListSortingUseCase = applyTokenListSortingUseCase, runPolkadotAccountHealthCheckUseCase = runPolkadotAccountHealthCheckUseCase, diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/MultiWalletContentLoaderFactory.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/MultiWalletContentLoaderFactory.kt index 1b405c8af6..7a70b865b2 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/MultiWalletContentLoaderFactory.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/MultiWalletContentLoaderFactory.kt @@ -2,12 +2,12 @@ package com.tangem.feature.wallet.presentation.wallet.loaders.implementors import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase import com.tangem.domain.tokens.ApplyTokenListSortingUseCase -import com.tangem.domain.tokens.GetTokenListUseCase import com.tangem.domain.tokens.RunPolkadotAccountHealthCheckUseCase import com.tangem.domain.wallets.models.UserWallet import com.tangem.feature.wallet.presentation.wallet.analytics.utils.TokenListAnalyticsSender import com.tangem.feature.wallet.presentation.wallet.analytics.utils.WalletWarningsAnalyticsSender import com.tangem.feature.wallet.presentation.wallet.domain.GetMultiWalletWarningsFactory +import com.tangem.feature.wallet.presentation.wallet.domain.MultiWalletTokenListStore import com.tangem.feature.wallet.presentation.wallet.domain.WalletWithFundsChecker import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntents @@ -21,7 +21,7 @@ internal class MultiWalletContentLoaderFactory @Inject constructor( private val tokenListAnalyticsSender: TokenListAnalyticsSender, private val walletWithFundsChecker: WalletWithFundsChecker, private val getMultiWalletWarningsFactory: GetMultiWalletWarningsFactory, - private val getTokenListUseCase: GetTokenListUseCase, + private val tokenListStore: MultiWalletTokenListStore, private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase, private val applyTokenListSortingUseCase: ApplyTokenListSortingUseCase, private val walletWarningsAnalyticsSender: WalletWarningsAnalyticsSender, @@ -35,7 +35,7 @@ internal class MultiWalletContentLoaderFactory @Inject constructor( stateHolder = stateHolder, tokenListAnalyticsSender = tokenListAnalyticsSender, walletWithFundsChecker = walletWithFundsChecker, - getTokenListUseCase = getTokenListUseCase, + tokenListStore = tokenListStore, getSelectedAppCurrencyUseCase = getSelectedAppCurrencyUseCase, getMultiWalletWarningsFactory = getMultiWalletWarningsFactory, walletWarningsAnalyticsSender = walletWarningsAnalyticsSender, diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/BasicTokenListSubscriber.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/BasicTokenListSubscriber.kt index f074661604..9c1bbbd713 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/BasicTokenListSubscriber.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/BasicTokenListSubscriber.kt @@ -41,11 +41,11 @@ internal abstract class BasicTokenListSubscriber( private val sendAnalyticsJobHolder = JobHolder() private val onTokenListReceivedJobHolder = JobHolder() - protected abstract fun tokenListFlow(): LceFlow + protected abstract fun tokenListFlow(coroutineScope: CoroutineScope): LceFlow override fun create(coroutineScope: CoroutineScope): Flow<*> { return combine( - flow = tokenListFlow() + flow = tokenListFlow(coroutineScope) .onEach { maybeTokenList -> coroutineScope.launch { sendTokenListAnalytics(maybeTokenList) diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/MultiWalletTokenListSubscriber.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/MultiWalletTokenListSubscriber.kt index 12d5115bcf..79cef234af 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/MultiWalletTokenListSubscriber.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/MultiWalletTokenListSubscriber.kt @@ -4,7 +4,6 @@ import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase import com.tangem.domain.core.lce.Lce import com.tangem.domain.core.lce.LceFlow import com.tangem.domain.tokens.ApplyTokenListSortingUseCase -import com.tangem.domain.tokens.GetTokenListUseCase import com.tangem.domain.tokens.RunPolkadotAccountHealthCheckUseCase import com.tangem.domain.tokens.error.TokenListError import com.tangem.domain.tokens.model.CryptoCurrency @@ -12,14 +11,16 @@ import com.tangem.domain.tokens.model.TokenList import com.tangem.domain.tokens.model.TotalFiatBalance import com.tangem.domain.wallets.models.UserWallet import com.tangem.feature.wallet.presentation.wallet.analytics.utils.TokenListAnalyticsSender +import com.tangem.feature.wallet.presentation.wallet.domain.MultiWalletTokenListStore import com.tangem.feature.wallet.presentation.wallet.domain.WalletWithFundsChecker import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntents +import kotlinx.coroutines.CoroutineScope @Suppress("LongParameterList") internal class MultiWalletTokenListSubscriber( private val userWallet: UserWallet, - private val getTokenListUseCase: GetTokenListUseCase, + private val tokenListStore: MultiWalletTokenListStore, private val applyTokenListSortingUseCase: ApplyTokenListSortingUseCase, stateHolder: WalletStateController, clickIntents: WalletClickIntents, @@ -37,8 +38,10 @@ internal class MultiWalletTokenListSubscriber( runPolkadotAccountHealthCheckUseCase = runPolkadotAccountHealthCheckUseCase, ) { - override fun tokenListFlow(): LceFlow { - return getTokenListUseCase.launch(userWallet.walletId) + override fun tokenListFlow(coroutineScope: CoroutineScope): LceFlow { + tokenListStore.addIfNot(userWallet.walletId, coroutineScope) + + return tokenListStore.getOrThrow(userWallet.walletId) } override suspend fun onTokenListReceived(maybeTokenList: Lce) { diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/SingleWalletWithTokenListSubscriber.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/SingleWalletWithTokenListSubscriber.kt index c2c561afd8..c8848d06e9 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/SingleWalletWithTokenListSubscriber.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/SingleWalletWithTokenListSubscriber.kt @@ -12,6 +12,7 @@ import com.tangem.feature.wallet.presentation.wallet.analytics.utils.TokenListAn import com.tangem.feature.wallet.presentation.wallet.domain.WalletWithFundsChecker import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntents +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.map @Suppress("LongParameterList") @@ -34,6 +35,9 @@ internal class SingleWalletWithTokenListSubscriber( runPolkadotAccountHealthCheckUseCase = runPolkadotAccountHealthCheckUseCase, ) { - override fun tokenListFlow(): LceFlow = getNodlTokenListUseCase(userWallet.walletId) - .map { it.toLce() } + override fun tokenListFlow(coroutineScope: CoroutineScope): LceFlow = + getNodlTokenListUseCase( + userWallet.walletId, + ) + .map { it.toLce() } } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt index 87496adf65..14bb262aa5 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt @@ -16,6 +16,7 @@ import com.tangem.feature.wallet.presentation.deeplink.WalletDeepLinksHandler import com.tangem.feature.wallet.presentation.router.InnerWalletRouter import com.tangem.feature.wallet.presentation.wallet.analytics.WalletScreenAnalyticsEvent import com.tangem.feature.wallet.presentation.wallet.analytics.utils.SelectedWalletAnalyticsSender +import com.tangem.feature.wallet.presentation.wallet.domain.MultiWalletTokenListStore import com.tangem.feature.wallet.presentation.wallet.domain.WalletImageResolver import com.tangem.feature.wallet.presentation.wallet.domain.WalletNameMigrationUseCase import com.tangem.feature.wallet.presentation.wallet.loaders.WalletScreenContentLoader @@ -69,6 +70,7 @@ internal class WalletViewModel @Inject constructor( private val shouldAskPermissionUseCase: ShouldAskPermissionUseCase, private val marketsFeatureToggles: MarketsFeatureToggles, private val walletImageResolver: WalletImageResolver, + private val tokenListStore: MultiWalletTokenListStore, analyticsEventsHandler: AnalyticsEventHandler, ) : ViewModel() { @@ -110,6 +112,8 @@ internal class WalletViewModel @Inject constructor( override fun onCleared() { super.onCleared() + + tokenListStore.clear() stateHolder.clear() walletScreenContentLoader.cancelAll() } @@ -280,7 +284,7 @@ internal class WalletViewModel @Inject constructor( stateHolder.update(transformer = RenameWalletTransformer(action.selectedWalletId, action.name)) } is WalletsUpdateActionResolver.Action.Unknown -> { - Timber.w("Unable to perfom action: $action") + Timber.w("Unable to perform action: $action") } } } @@ -318,6 +322,7 @@ internal class WalletViewModel @Inject constructor( private fun reinitializeWallet(action: WalletsUpdateActionResolver.Action.ReinitializeWallet) { walletScreenContentLoader.cancel(action.prevWalletId) + tokenListStore.remove(action.prevWalletId) walletScreenContentLoader.load( userWallet = action.selectedWallet, @@ -357,6 +362,7 @@ internal class WalletViewModel @Inject constructor( private suspend fun deleteWallet(action: WalletsUpdateActionResolver.Action.DeleteWallet) { walletScreenContentLoader.cancel(action.deletedWalletId) + tokenListStore.remove(action.deletedWalletId) walletScreenContentLoader.load( userWallet = action.selectedWallet, diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletsUpdateActionResolver.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletsUpdateActionResolver.kt index 835a480b48..324b2fc7ca 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletsUpdateActionResolver.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletsUpdateActionResolver.kt @@ -136,7 +136,9 @@ internal class WalletsUpdateActionResolver @Inject constructor( unlockedWallets = wallets.filterNot(UserWallet::isLocked), ) } - isSelectedWalletCardsCountChanged(state, selectedWallet) -> Action.UpdateWalletCardCount(selectedWallet) + isSelectedWalletCardsCountChanged(state, selectedWallet) -> { + Action.UpdateWalletCardCount(selectedWallet) + } else -> Action.Unknown } } diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletCardClickIntents.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletCardClickIntents.kt index 7c128ca00d..066fe95381 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletCardClickIntents.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletCardClickIntents.kt @@ -12,6 +12,7 @@ import com.tangem.domain.wallets.models.UserWalletId import com.tangem.domain.wallets.usecase.* import com.tangem.feature.wallet.impl.R import com.tangem.feature.wallet.presentation.wallet.analytics.WalletScreenAnalyticsEvent.MainScreen +import com.tangem.feature.wallet.presentation.wallet.domain.MultiWalletTokenListStore import com.tangem.feature.wallet.presentation.wallet.loaders.WalletScreenContentLoader import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController import com.tangem.feature.wallet.presentation.wallet.state.model.WalletAlertState @@ -37,6 +38,7 @@ internal interface WalletCardClickIntents { @Suppress("LongParameterList") internal class WalletCardClickIntentsImplementor @Inject constructor( private val stateHolder: WalletStateController, + private val tokenListStore: MultiWalletTokenListStore, private val walletEventSender: WalletEventSender, private val walletScreenContentLoader: WalletScreenContentLoader, private val renameWalletUseCase: RenameWalletUseCase, @@ -99,6 +101,7 @@ internal class WalletCardClickIntentsImplementor @Inject constructor( override fun onDeleteAfterConfirmationClick(userWalletId: UserWalletId) { viewModelScope.launch(dispatchers.main) { walletScreenContentLoader.cancel(userWalletId) + tokenListStore.remove(userWalletId) val walletToDelete = getUserWalletUseCase(userWalletId).getOrNull() ?: return@launch val hasUserWallets = deleteWalletUseCase(userWalletId).getOrElse { @@ -117,6 +120,7 @@ internal class WalletCardClickIntentsImplementor @Inject constructor( reduxStateHolder.onUserWalletSelected(selectedWallet) } else { + tokenListStore.clear() stateHolder.clear() appRouter.replaceAll(AppRoute.Home) } diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletClickIntents.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletClickIntents.kt index ec3f2d992d..a1ddf993ae 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletClickIntents.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletClickIntents.kt @@ -8,6 +8,7 @@ import com.tangem.domain.settings.NeverToShowWalletsScrollPreview import com.tangem.domain.tokens.FetchCardTokenListUseCase import com.tangem.domain.tokens.FetchCurrencyStatusUseCase import com.tangem.domain.tokens.FetchTokenListUseCase +import com.tangem.domain.tokens.FetchTokenListUseCase.RefreshMode import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase import com.tangem.domain.wallets.usecase.SelectWalletUseCase import com.tangem.feature.wallet.presentation.router.InnerWalletRouter @@ -117,7 +118,7 @@ internal class WalletClickIntents @Inject constructor( val maybeFetchResult = if (userWallet.scanResponse.cardTypesResolver.isSingleWalletWithToken()) { fetchCardTokenListUseCase(userWalletId = userWallet.walletId, refresh = true) } else { - fetchTokenListUseCase(userWalletId = userWallet.walletId, refresh = true) + fetchTokenListUseCase(userWalletId = userWallet.walletId, mode = RefreshMode.FULL) } maybeFetchResult.onLeft { diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletWarningsClickIntents.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletWarningsClickIntents.kt index e7386f4925..fb53cabe3d 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletWarningsClickIntents.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletWarningsClickIntents.kt @@ -13,6 +13,7 @@ import com.tangem.domain.redux.LegacyAction import com.tangem.domain.redux.ReduxStateHolder import com.tangem.domain.settings.* import com.tangem.domain.tokens.FetchTokenListUseCase +import com.tangem.domain.tokens.FetchTokenListUseCase.RefreshMode import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.tokens.model.analytics.TokenSwapPromoAnalyticsEvent import com.tangem.domain.wallets.legacy.UserWalletsListManager.Lockable.UnlockType @@ -124,18 +125,19 @@ internal class WalletWarningsClickIntentsImplementor @Inject constructor( analyticsEventHandler.send(Basic.CardWasScanned(AnalyticsParam.ScreensSources.Main)) analyticsEventHandler.send(MainScreen.NoticeScanYourCardTapped) - viewModelScope.launch(dispatchers.main) { + viewModelScope.launch { val userWallet = getSelectedUserWallet() ?: return@launch derivePublicKeysUseCase( userWalletId = userWallet.walletId, currencies = missedAddressCurrencies, - ) - .onRight { - // Refresh must be set to true to ensure that yield balances are updated - fetchTokenListUseCase(userWalletId = userWallet.walletId, refresh = true) - } - .onLeft { Timber.e("Failed to derive public keys: $it") } + ).onLeft { + Timber.e(it, "Failed to derive public keys") + return@launch + } + + // Refresh must be set to true to ensure that yield balances are updated + fetchTokenListUseCase(userWallet.walletId, mode = RefreshMode.SKIP_CURRENCIES) } } From 568e735d2368dc4dac54b56eeb2aa5551be7d051 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 17 Oct 2024 22:28:06 +0400 Subject: [PATCH 012/106] Updated on 2026-08-14 --- .../data/staking/DefaultStakingRepository.kt | 101 ++++++------------ .../repository/DefaultCurrenciesRepository.kt | 45 +------- .../repository/DefaultNetworksRepository.kt | 88 +-------------- .../staking/repositories/StakingRepository.kt | 7 +- .../CurrenciesStatusesLceOperations.kt | 2 +- .../tokens/repository/CurrenciesRepository.kt | 12 --- .../tokens/repository/NetworksRepository.kt | 41 ------- .../repository/MockCurrenciesRepository.kt | 10 +- .../repository/MockNetworksRepository.kt | 38 ------- .../repository/MockStakingRepository.kt | 13 +-- 10 files changed, 43 insertions(+), 314 deletions(-) diff --git a/data/staking/src/main/java/com/tangem/data/staking/DefaultStakingRepository.kt b/data/staking/src/main/java/com/tangem/data/staking/DefaultStakingRepository.kt index 8633e9d88c..bbf7601d27 100644 --- a/data/staking/src/main/java/com/tangem/data/staking/DefaultStakingRepository.kt +++ b/data/staking/src/main/java/com/tangem/data/staking/DefaultStakingRepository.kt @@ -101,10 +101,6 @@ internal class DefaultStakingRepository( private val yieldBalanceConverter = YieldBalanceConverter() private val yieldBalanceListConverter = YieldBalanceListConverter(yieldBalanceConverter) - private val isYieldBalanceFetching = MutableStateFlow( - value = emptyMap(), - ) - private val tronStakeKitTransactionAdapter by lazy { moshi.adapter(TronStakeKitTransaction::class.java) } override fun getIntegrationKey(cryptoCurrencyId: CryptoCurrency.ID): String = with(cryptoCurrencyId) { @@ -384,79 +380,48 @@ internal class DefaultStakingRepository( ) = withContext(dispatchers.io) { if (!stakingFeatureToggle.isStakingEnabled) return@withContext - try { - cacheRegistry.invokeOnExpire( - key = getYieldBalancesKey(userWalletId), - skipCache = refresh, - block = { - isYieldBalanceFetching.update { - it + (userWalletId to true) - } + cacheRegistry.invokeOnExpire( + key = getYieldBalancesKey(userWalletId), + skipCache = refresh, + block = { + val yields = getEnabledYields().ifEmpty { + Timber.i("No enabled yields for $userWalletId") + stakingBalanceStore.store(userWalletId, emptySet()) - val yields = getEnabledYields().ifEmpty { - Timber.i("No enabled yields for $userWalletId") + return@invokeOnExpire + } + val availableCurrencies = cryptoCurrencies + .mapNotNull { currency -> + val addresses = walletManagersFacade.getAddresses(userWalletId, currency.network) + val integrationId = integrationIdMap[getIntegrationKey(currency.id)] + + if (integrationId != null && yields.any { it.id == integrationId }) { + addresses to integrationId + } else { + null + } + } + .flatMap { (addresses, integrationId) -> + addresses.map { address -> address to integrationId } + } + .map { getBalanceRequestData(it.first.value, it.second) } + .ifEmpty { + Timber.i("No yield balances available for $userWalletId") stakingBalanceStore.store(userWalletId, emptySet()) return@invokeOnExpire } - val availableCurrencies = cryptoCurrencies - .mapNotNull { currency -> - val addresses = walletManagersFacade.getAddresses(userWalletId, currency.network) - val integrationId = integrationIdMap[getIntegrationKey(currency.id)] - if (integrationId != null && yields.any { it.id == integrationId }) { - addresses to integrationId - } else { - null - } - } - .flatMap { (addresses, integrationId) -> - addresses.map { address -> address to integrationId } - } - .map { getBalanceRequestData(it.first.value, it.second) } - .ifEmpty { - Timber.i("No yield balances available for $userWalletId") - stakingBalanceStore.store(userWalletId, emptySet()) + val result = stakeKitApi + .getMultipleYieldBalances(availableCurrencies) + .getOrThrow() - return@invokeOnExpire - } - - val result = stakeKitApi - .getMultipleYieldBalances(availableCurrencies) - .getOrThrow() - - stakingBalanceStore.store(userWalletId, result) - }, - ) - } finally { - isYieldBalanceFetching.update { - it - userWalletId - } - } + stakingBalanceStore.store(userWalletId, result) + }, + ) } - override fun getMultiYieldBalanceFlow( - userWalletId: UserWalletId, - cryptoCurrencies: List, - ): Flow = channelFlow { - if (!stakingFeatureToggle.isStakingEnabled) { - send(YieldBalanceList.Empty) - } else { - launch(dispatchers.io) { - stakingBalanceStore.get(userWalletId) - .collectLatest { send(yieldBalanceListConverter.convert(it)) } - } - - withContext(dispatchers.io) { - fetchMultiYieldBalance( - userWalletId, - cryptoCurrencies, - ) - } - } - }.cancellable() - - override fun getMultiYieldBalance( + override fun getMultiYieldBalanceUpdates( userWalletId: UserWalletId, cryptoCurrencies: List, ): Flow = channelFlow { diff --git a/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultCurrenciesRepository.kt b/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultCurrenciesRepository.kt index 4664e120ce..e5b5f561bd 100644 --- a/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultCurrenciesRepository.kt +++ b/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultCurrenciesRepository.kt @@ -1,6 +1,5 @@ package com.tangem.data.tokens.repository -import arrow.core.raise.catch import com.tangem.blockchain.common.Blockchain import com.tangem.blockchainsdk.compatibility.getL2CompatibilityTokenComparison import com.tangem.blockchainsdk.utils.toCoinId @@ -27,8 +26,6 @@ import com.tangem.datasource.local.preferences.utils.storeObject import com.tangem.datasource.local.token.ExpressAssetsStore import com.tangem.datasource.local.userwallet.UserWalletsStore import com.tangem.domain.core.error.DataError -import com.tangem.domain.core.lce.LceFlow -import com.tangem.domain.core.lce.lceFlow import com.tangem.domain.demo.DemoConfig import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.tokens.model.CryptoCurrencyStatus @@ -67,10 +64,6 @@ internal class DefaultCurrenciesRepository( private val userTokensBackwardCompatibility = UserTokensBackwardCompatibility() private val customTokensMerger = CustomTokensMerger(tangemTechApi, dispatchers) - private val isMultiCurrencyWalletCurrenciesFetching = MutableStateFlow( - value = emptyMap(), - ) - override suspend fun saveTokens( userWalletId: UserWalletId, currencies: List, @@ -267,30 +260,6 @@ internal class DefaultCurrenciesRepository( } } - override fun getMultiCurrencyWalletCurrenciesUpdatesLce( - userWalletId: UserWalletId, - ): LceFlow> = lceFlow { - val userWallet = getUserWallet(userWalletId) - catch({ ensureIsCorrectUserWallet(userWallet, isMultiCurrencyWalletExpected = true) }) { - raise(it) - } - - launch(dispatchers.io) { - combine( - getMultiCurrencyWalletCurrencies(userWallet), - isMultiCurrencyWalletCurrenciesFetching.map { it.getOrElse(userWallet.walletId) { false } }, - ) { currencies, isFetching -> - send(currencies, isStillLoading = isFetching) - }.collect() - } - - withContext(dispatchers.io) { - catch({ fetchTokensIfCacheExpired(userWallet, refresh = false) }) { - raise(it) - } - } - } - override suspend fun getMultiCurrencyWalletCurrenciesSync( userWalletId: UserWalletId, refresh: Boolean, @@ -540,19 +509,7 @@ internal class DefaultCurrenciesRepository( cacheRegistry.invokeOnExpire( key = getTokensCacheKey(userWallet.walletId), skipCache = refresh, - block = { - isMultiCurrencyWalletCurrenciesFetching.update { - it + (userWallet.walletId to true) - } - - try { - fetchTokens(userWallet) - } finally { - isMultiCurrencyWalletCurrenciesFetching.update { - it - userWallet.walletId - } - } - }, + block = { fetchTokens(userWallet) }, ) } diff --git a/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultNetworksRepository.kt b/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultNetworksRepository.kt index d61f12d1a3..26ab1470f2 100644 --- a/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultNetworksRepository.kt +++ b/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultNetworksRepository.kt @@ -1,6 +1,5 @@ package com.tangem.data.tokens.repository -import arrow.core.raise.catch import com.tangem.blockchain.common.Blockchain import com.tangem.blockchain.common.address.AddressType import com.tangem.blockchainsdk.utils.fromNetworkId @@ -15,8 +14,6 @@ import com.tangem.datasource.local.preferences.PreferencesKeys import com.tangem.datasource.local.preferences.utils.getObjectSyncOrNull import com.tangem.datasource.local.userwallet.UserWalletsStore import com.tangem.domain.common.util.cardTypesResolver -import com.tangem.domain.core.lce.LceFlow -import com.tangem.domain.core.lce.lceFlow import com.tangem.domain.demo.DemoConfig import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.tokens.model.CryptoCurrencyAddress @@ -28,7 +25,10 @@ import com.tangem.domain.walletmanager.model.UpdateWalletManagerResult import com.tangem.domain.wallets.models.UserWalletId import com.tangem.utils.coroutines.CoroutineDispatcherProvider import kotlinx.coroutines.* -import kotlinx.coroutines.flow.* +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.channelFlow +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach import timber.log.Timber @Suppress("LongParameterList") @@ -46,10 +46,6 @@ internal class DefaultNetworksRepository( private val responseCurrenciesFactory by lazy { ResponseCryptoCurrenciesFactory() } private val networkStatusFactory by lazy { NetworkStatusFactory() } - private val isNetworkStatusesFetching = MutableStateFlow( - value = emptyMap(), - ) - override fun getNetworkStatusesUpdates( userWalletId: UserWalletId, networks: Set, @@ -63,24 +59,6 @@ internal class DefaultNetworksRepository( } } - override fun getNetworkStatusesUpdatesLce( - userWalletId: UserWalletId, - networks: Set, - ): LceFlow> = lceFlow { - combine( - networksStatusesStore.get(userWalletId), - isNetworkStatusesFetching.map { it.getOrElse(userWalletId) { false } }, - ) { statuses, isFetching -> - send(statuses, isStillLoading = isFetching || networks.size != statuses.size) - }.launchIn(scope = this + dispatchers.io) - - withContext(dispatchers.io) { - catch({ fetchNetworksStatusesIfCacheExpired(userWalletId, networks, refresh = false) }) { - raise(it) - } - } - } - override suspend fun fetchNetworkPendingTransactions(userWalletId: UserWalletId, networks: Set) { val currencies = getCurrencies(userWalletId, networks) withContext(dispatchers.io) { @@ -123,67 +101,13 @@ internal class DefaultNetworksRepository( } } - override suspend fun getNetworkAddress( - userWalletId: UserWalletId, - currency: CryptoCurrency, - ): CryptoCurrencyAddress = withContext(dispatchers.io) { - CryptoCurrencyAddress( - cryptoCurrency = currency, - address = walletManagersFacade.getAddresses(userWalletId, currency.network) - .firstOrNull { it.type == AddressType.Default } - ?.value.orEmpty(), - ) - } - - override fun getNetworkAddressFlow( - userWalletId: UserWalletId, - currency: CryptoCurrency, - ): Flow = channelFlow { - launch(dispatchers.io) { - send(getNetworkAddress(userWalletId, currency)) - } - } - - override suspend fun getNetworkAddresses(userWalletId: UserWalletId): List = - withContext(dispatchers.io) { - // Get list of currencies matching [network] - val currencies = getCurrencies(userWalletId) - - // There is no currencies matching given [networks] in [userWalletId] - if (currencies.toList().isEmpty()) return@withContext emptyList() - - currencies.toList().map { currency -> - CryptoCurrencyAddress( - cryptoCurrency = currency, - address = walletManagersFacade.getAddresses(userWalletId, currency.network) - .firstOrNull { it.type == AddressType.Default } - ?.value.orEmpty(), - ) - } - } - - override fun getNetworkAddressesFlow( - userWalletId: UserWalletId, - network: Network, - ): Flow> = channelFlow { - launch(dispatchers.io) { - send(getNetworkAddresses(userWalletId, network)) - } - } - - override fun getNetworkAddressesFlow(userWalletId: UserWalletId): Flow> = channelFlow { - launch(dispatchers.io) { - send(getNetworkAddresses(userWalletId)) - } - } - private suspend fun fetchNetworksStatusesIfCacheExpired( userWalletId: UserWalletId, networks: Set, refresh: Boolean, ) = coroutineScope { if (refresh) { - val statusesToRefresh = networks.map { NetworkStatus(it, NetworkStatus.Loading) } + val statusesToRefresh = networks.map { NetworkStatus(it, NetworkStatus.Refreshing) } networksStatusesStore.storeAll(userWalletId, statusesToRefresh) } @@ -230,8 +154,6 @@ internal class DefaultNetworksRepository( network: Network, currencies: Sequence, ) { - networksStatusesStore.store(userWalletId, NetworkStatus(network, NetworkStatus.Loading)) - val result = walletManagersFacade.update( userWalletId = userWalletId, network = network, diff --git a/domain/staking/src/main/java/com/tangem/domain/staking/repositories/StakingRepository.kt b/domain/staking/src/main/java/com/tangem/domain/staking/repositories/StakingRepository.kt index 6e17391a27..568683be77 100644 --- a/domain/staking/src/main/java/com/tangem/domain/staking/repositories/StakingRepository.kt +++ b/domain/staking/src/main/java/com/tangem/domain/staking/repositories/StakingRepository.kt @@ -49,12 +49,7 @@ interface StakingRepository { refresh: Boolean = false, ) - fun getMultiYieldBalanceFlow( - userWalletId: UserWalletId, - cryptoCurrencies: List, - ): Flow - - fun getMultiYieldBalance( + fun getMultiYieldBalanceUpdates( userWalletId: UserWalletId, cryptoCurrencies: List, ): Flow diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/CurrenciesStatusesLceOperations.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/CurrenciesStatusesLceOperations.kt index 730dbc541b..cc96309f1f 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/CurrenciesStatusesLceOperations.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/CurrenciesStatusesLceOperations.kt @@ -186,7 +186,7 @@ internal class CurrenciesStatusesLceOperations( userWalletId: UserWalletId, cryptoCurrencies: List, ): EitherFlow { - return stakingRepository.getMultiYieldBalance(userWalletId, cryptoCurrencies) + return stakingRepository.getMultiYieldBalanceUpdates(userWalletId, cryptoCurrencies) .map> { it.right() } .catch { emit(TokenListError.DataError(it).left()) } .distinctUntilChanged() diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/CurrenciesRepository.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/CurrenciesRepository.kt index 8a19b705e9..bda0e8fdbd 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/CurrenciesRepository.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/CurrenciesRepository.kt @@ -1,7 +1,6 @@ package com.tangem.domain.tokens.repository import com.tangem.domain.core.error.DataError -import com.tangem.domain.core.lce.LceFlow import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.domain.tokens.model.FeePaidCurrency @@ -130,17 +129,6 @@ interface CurrenciesRepository { */ fun getMultiCurrencyWalletCurrenciesUpdates(userWalletId: UserWalletId): Flow> - /** - * Retrieves updates of the list of cryptocurrencies within a multi-currency wallet. - * - * Loads remote cryptocurrencies if they have expired. - * - * @param userWalletId The unique identifier of the user wallet. - * @return A [LceFlow] emitting the set of cryptocurrencies associated with the user wallet. May emit an - * [DataError.UserWalletError.WrongUserWallet] if single-currency user wallet ID provided. - */ - fun getMultiCurrencyWalletCurrenciesUpdatesLce(userWalletId: UserWalletId): LceFlow> - /** * Retrieves the list of cryptocurrencies within a multi-currency wallet. * diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/NetworksRepository.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/NetworksRepository.kt index a2a99ddea2..2e568b83b4 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/NetworksRepository.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/NetworksRepository.kt @@ -1,7 +1,5 @@ package com.tangem.domain.tokens.repository -import com.tangem.domain.core.lce.LceFlow -import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.tokens.model.CryptoCurrencyAddress import com.tangem.domain.tokens.model.Network import com.tangem.domain.tokens.model.NetworkStatus @@ -23,20 +21,6 @@ interface NetworksRepository { */ fun getNetworkStatusesUpdates(userWalletId: UserWalletId, networks: Set): Flow> - /** - * Retrieves updates of network statuses of specified blockchain networks for a specific user wallet. - * - * Loads remote network statuses if they have expired. - * - * @param userWalletId The unique identifier of the user wallet. - * @param networks A set of network which statuses are to be retrieved. - * @return A [LceFlow] emitting a set of [NetworkStatus] objects corresponding to the specified networks. - */ - fun getNetworkStatusesUpdatesLce( - userWalletId: UserWalletId, - networks: Set, - ): LceFlow> - /** * Fetches pending transactions for given network * @@ -63,33 +47,8 @@ interface NetworksRepository { fun isNeedToCreateAccountWithoutReserve(network: Network): Boolean - /** - * Returns list of addresses and crypto currency info of added currencies of [network] in selected wallet [userWalletId] - */ - fun getNetworkAddressesFlow(userWalletId: UserWalletId, network: Network): Flow> - /** * Returns list of addresses and crypto currency info of added currencies of [network] in selected wallet [userWalletId] */ suspend fun getNetworkAddresses(userWalletId: UserWalletId, network: Network): List - - /** - * Returns address of [cryptoCurrency] in selected wallet [userWalletId] - */ - suspend fun getNetworkAddress(userWalletId: UserWalletId, currency: CryptoCurrency): CryptoCurrencyAddress - - /** - * Returns address of [cryptoCurrency] in selected wallet [userWalletId] - */ - fun getNetworkAddressFlow(userWalletId: UserWalletId, currency: CryptoCurrency): Flow - - /** - * Returns list of addresses and crypto currency info in selected wallet [userWalletId] - */ - fun getNetworkAddressesFlow(userWalletId: UserWalletId): Flow> - - /** - * Returns list of addresses and crypto currency info in selected wallet [userWalletId] - */ - suspend fun getNetworkAddresses(userWalletId: UserWalletId): List } \ No newline at end of file diff --git a/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/repository/MockCurrenciesRepository.kt b/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/repository/MockCurrenciesRepository.kt index cc16af1aba..f68979c6f4 100644 --- a/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/repository/MockCurrenciesRepository.kt +++ b/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/repository/MockCurrenciesRepository.kt @@ -3,8 +3,6 @@ package com.tangem.domain.tokens.repository import arrow.core.Either import arrow.core.getOrElse import com.tangem.domain.core.error.DataError -import com.tangem.domain.core.lce.LceFlow -import com.tangem.domain.core.utils.toLce import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.domain.tokens.model.FeePaidCurrency @@ -57,7 +55,7 @@ internal class MockCurrenciesRepository( override suspend fun removeCurrencies(userWalletId: UserWalletId, currencies: List) = Unit - override fun getWalletCurrenciesUpdates(userWalletId: UserWalletId): LceFlow> { + override fun getWalletCurrenciesUpdates(userWalletId: UserWalletId): Flow> { return emptyFlow() } @@ -91,12 +89,6 @@ internal class MockCurrenciesRepository( return tokens.map { it.getOrElse { e -> throw e } } } - override fun getMultiCurrencyWalletCurrenciesUpdatesLce( - userWalletId: UserWalletId, - ): LceFlow> { - return tokens.map { it.toLce() } - } - override suspend fun getMultiCurrencyWalletCurrency( userWalletId: UserWalletId, id: CryptoCurrency.ID, diff --git a/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/repository/MockNetworksRepository.kt b/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/repository/MockNetworksRepository.kt index 5b5019dfd6..2e56eb6aa0 100644 --- a/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/repository/MockNetworksRepository.kt +++ b/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/repository/MockNetworksRepository.kt @@ -3,15 +3,11 @@ package com.tangem.domain.tokens.repository import arrow.core.Either import arrow.core.getOrElse import com.tangem.domain.core.error.DataError -import com.tangem.domain.core.lce.LceFlow -import com.tangem.domain.core.utils.toLce -import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.tokens.model.CryptoCurrencyAddress import com.tangem.domain.tokens.model.Network import com.tangem.domain.tokens.model.NetworkStatus import com.tangem.domain.wallets.models.UserWalletId import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.channelFlow import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.map @@ -26,13 +22,6 @@ internal class MockNetworksRepository( return statuses.map { it.getOrElse { e -> throw e } } } - override fun getNetworkStatusesUpdatesLce( - userWalletId: UserWalletId, - networks: Set, - ): LceFlow> { - return statuses.map { it.toLce() } - } - override suspend fun fetchNetworkPendingTransactions(userWalletId: UserWalletId, networks: Set) { // no-op } @@ -47,37 +36,10 @@ internal class MockNetworksRepository( override fun isNeedToCreateAccountWithoutReserve(network: Network) = false - override fun getNetworkAddressesFlow( - userWalletId: UserWalletId, - network: Network, - ): Flow> = channelFlow { - send(emptyList()) - } - - override fun getNetworkAddressesFlow(userWalletId: UserWalletId): Flow> = channelFlow { - send(emptyList()) - } - override suspend fun getNetworkAddresses( userWalletId: UserWalletId, network: Network, ): List { return emptyList() } - - override suspend fun getNetworkAddresses(userWalletId: UserWalletId): List { - return emptyList() - } - - override suspend fun getNetworkAddress( - userWalletId: UserWalletId, - currency: CryptoCurrency, - ): CryptoCurrencyAddress = CryptoCurrencyAddress(currency, "") - - override fun getNetworkAddressFlow( - userWalletId: UserWalletId, - currency: CryptoCurrency, - ): Flow = channelFlow { - send(CryptoCurrencyAddress(currency, "")) - } } \ No newline at end of file diff --git a/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/repository/MockStakingRepository.kt b/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/repository/MockStakingRepository.kt index 9b702bacbb..0dc93edf8d 100644 --- a/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/repository/MockStakingRepository.kt +++ b/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/repository/MockStakingRepository.kt @@ -145,18 +145,7 @@ class MockStakingRepository : StakingRepository { /* no-op */ } - override fun getMultiYieldBalanceFlow( - userWalletId: UserWalletId, - cryptoCurrencies: List, - ): Flow = channelFlow { - send( - YieldBalanceList.Data( - balances = listOf(YieldBalance.Error), - ), - ) - } - - override fun getMultiYieldBalance( + override fun getMultiYieldBalanceUpdates( userWalletId: UserWalletId, cryptoCurrencies: List, ): Flow = flowOf() From ba0d4ff055ca73473b4059c9b494dbbca75a21e9 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 22 Oct 2024 13:31:04 +0400 Subject: [PATCH 013/106] Updated on 2026-08-14 --- .../preview/PreviewManageTokensComponent.kt | 2 ++ .../entity/managetokens/ManageTokensUM.kt | 3 +++ .../managetokens/model/ManageTokensModel.kt | 23 ++++++++++++++---- .../managetokens/ui/ManageTokensScreen.kt | 24 ++++++++++++++++--- 4 files changed, 45 insertions(+), 7 deletions(-) diff --git a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/component/preview/PreviewManageTokensComponent.kt b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/component/preview/PreviewManageTokensComponent.kt index a0058fc2e9..abecdf97cb 100644 --- a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/component/preview/PreviewManageTokensComponent.kt +++ b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/component/preview/PreviewManageTokensComponent.kt @@ -27,6 +27,7 @@ import kotlinx.coroutines.flow.update internal class PreviewManageTokensComponent( private val isLoading: Boolean, + private val showTangemIcon: Boolean, params: ManageTokensComponent.Params, ) : ManageTokensComponent { @@ -65,6 +66,7 @@ internal class PreviewManageTokensComponent( loadMore = { false }, saveChanges = {}, isSavingInProgress = false, + needToAddDerivations = showTangemIcon, ), ) diff --git a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/entity/managetokens/ManageTokensUM.kt b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/entity/managetokens/ManageTokensUM.kt index d16e17e84a..708b149aca 100644 --- a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/entity/managetokens/ManageTokensUM.kt +++ b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/entity/managetokens/ManageTokensUM.kt @@ -42,6 +42,7 @@ internal sealed class ManageTokensUM { val saveChanges: () -> Unit, val hasChanges: Boolean, val isSavingInProgress: Boolean, + val needToAddDerivations: Boolean, ) : ManageTokensUM() fun copySealed( @@ -52,6 +53,7 @@ internal sealed class ManageTokensUM { isNextBatchLoading: Boolean = this.isNextBatchLoading, isSavingInProgress: Boolean = this is ManageContent && this.isSavingInProgress, scrollToTop: StateEvent = this.scrollToTop, + needToAddDerivations: Boolean = this is ManageContent && this.needToAddDerivations, ): ManageTokensUM { return when (this) { is ManageContent -> copy( @@ -62,6 +64,7 @@ internal sealed class ManageTokensUM { isNextBatchLoading = isNextBatchLoading, isSavingInProgress = isSavingInProgress, scrollToTop = scrollToTop, + needToAddDerivations = needToAddDerivations, ) is ReadContent -> copy( search = search, diff --git a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/model/ManageTokensModel.kt b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/model/ManageTokensModel.kt index f5375b9b43..36916e335d 100644 --- a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/model/ManageTokensModel.kt +++ b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/model/ManageTokensModel.kt @@ -17,6 +17,7 @@ import com.tangem.core.ui.event.triggeredEvent import com.tangem.core.ui.extensions.resourceReference import com.tangem.core.ui.extensions.stringReference import com.tangem.core.ui.message.SnackbarMessage +import com.tangem.domain.card.HasMissedDerivationsUseCase import com.tangem.domain.managetokens.SaveManagedTokensUseCase import com.tangem.domain.wallets.models.UserWalletId import com.tangem.features.managetokens.analytics.CustomTokenAnalyticsEvent @@ -47,6 +48,7 @@ internal class ManageTokensModel @Inject constructor( private val router: Router, private val manageTokensListManager: ManageTokensListManager, private val messageSender: UiMessageSender, + private val hasMissedDerivationsUseCase: HasMissedDerivationsUseCase, private val saveManagedTokensUseCase: SaveManagedTokensUseCase, private val analyticsEventHandler: AnalyticsEventHandler, paramsContainer: ParamsContainer, @@ -140,6 +142,7 @@ internal class ManageTokensModel @Inject constructor( hasChanges = false, saveChanges = ::saveChanges, loadMore = ::loadMoreItems, + needToAddDerivations = false, isSavingInProgress = false, ) } @@ -255,10 +258,22 @@ internal class ManageTokensModel @Inject constructor( } private fun updateChangedItems(currenciesToAdd: ChangedCurrencies, currenciesToRemove: ChangedCurrencies) { - state.update { state -> - state.copySealed( - hasChanges = currenciesToAdd.isNotEmpty() || currenciesToRemove.isNotEmpty(), - ) + modelScope.launch { + val hasMissedDerivations = params.userWalletId?.let { walletId -> + val networks = currenciesToAdd.values + .flatten() + .toSet() + .associate { it.backendId to null } + + hasMissedDerivationsUseCase(walletId, networks) + } + + state.update { state -> + state.copySealed( + hasChanges = currenciesToAdd.isNotEmpty() || currenciesToRemove.isNotEmpty(), + needToAddDerivations = hasMissedDerivations ?: false, + ) + } } } diff --git a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/ui/ManageTokensScreen.kt b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/ui/ManageTokensScreen.kt index 6b2547a8fd..2d604c67ee 100644 --- a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/ui/ManageTokensScreen.kt +++ b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/ui/ManageTokensScreen.kt @@ -33,11 +33,17 @@ import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.PreviewParameterProvider import androidx.compose.ui.unit.dp import androidx.compose.ui.util.fastForEachIndexed -import com.tangem.core.ui.components.* +import com.tangem.core.ui.components.BottomFade +import com.tangem.core.ui.components.RectangleShimmer +import com.tangem.core.ui.components.TangemSwitch +import com.tangem.core.ui.components.TextShimmer import com.tangem.core.ui.components.appbar.TangemTopAppBar import com.tangem.core.ui.components.appbar.models.TopAppBarButtonUM import com.tangem.core.ui.components.buttons.SecondarySmallButton import com.tangem.core.ui.components.buttons.SmallButtonConfig +import com.tangem.core.ui.components.buttons.common.TangemButton +import com.tangem.core.ui.components.buttons.common.TangemButtonIconPosition +import com.tangem.core.ui.components.buttons.common.TangemButtonsDefaults import com.tangem.core.ui.components.currency.icon.CurrencyIcon import com.tangem.core.ui.components.currency.icon.CurrencyIconState import com.tangem.core.ui.components.fields.SearchBar @@ -122,6 +128,7 @@ internal fun ManageTokensScreen(state: ManageTokensUM, modifier: Modifier = Modi .fillMaxWidth(), isVisible = state.hasChanges, showProgress = state.isSavingInProgress, + showIcon = state.needToAddDerivations, onClick = state.saveChanges, ) } @@ -158,6 +165,7 @@ private fun ManageTokensTopBar(topBar: ManageTokensTopBarUM?, search: SearchBarU private fun SaveChangesButton( isVisible: Boolean, showProgress: Boolean, + showIcon: Boolean, onClick: () -> Unit, modifier: Modifier = Modifier, ) { @@ -168,10 +176,17 @@ private fun SaveChangesButton( exit = fadeOut(), label = "save_button_visibility", ) { - PrimaryButtonIconEnd( + TangemButton( text = stringResource(id = R.string.common_save), - iconResId = R.drawable.ic_tangem_24, + icon = if (showIcon) { + TangemButtonIconPosition.End(R.drawable.ic_tangem_24) + } else { + TangemButtonIconPosition.None + }, showProgress = showProgress, + colors = TangemButtonsDefaults.primaryButtonColors, + enabled = true, + animateContentChange = true, onClick = onClick, ) } @@ -472,6 +487,7 @@ private class PreviewManageTokensComponentProvider : PreviewParameterProvider Date: Tue, 22 Oct 2024 19:28:49 +0400 Subject: [PATCH 014/106] Updated on 2026-08-14 --- .../entity/customtoken/CustomTokenFormUM.kt | 1 + .../managetokens/model/CustomTokenFormModel.kt | 10 +++++++++- .../managetokens/ui/CustomTokenFormContent.kt | 13 +++++++++++-- .../utils/ui/CustomCurrencyFormOperations.kt | 2 ++ 4 files changed, 23 insertions(+), 3 deletions(-) diff --git a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/entity/customtoken/CustomTokenFormUM.kt b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/entity/customtoken/CustomTokenFormUM.kt index b4fbaea7be..8f3c97fef8 100644 --- a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/entity/customtoken/CustomTokenFormUM.kt +++ b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/entity/customtoken/CustomTokenFormUM.kt @@ -14,6 +14,7 @@ internal data class CustomTokenFormUM( val notifications: PersistentList = persistentListOf(), val canAddToken: Boolean = false, val isValidating: Boolean = false, + val needToAddDerivation: Boolean = false, val saveToken: () -> Unit, ) { diff --git a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/model/CustomTokenFormModel.kt b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/model/CustomTokenFormModel.kt index c1c6f9922e..f6c43189f7 100644 --- a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/model/CustomTokenFormModel.kt +++ b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/model/CustomTokenFormModel.kt @@ -12,6 +12,7 @@ import com.tangem.core.ui.extensions.resourceReference import com.tangem.core.ui.extensions.stringReference import com.tangem.core.ui.message.ContentMessage import com.tangem.domain.card.DerivePublicKeysUseCase +import com.tangem.domain.card.HasMissedDerivationsUseCase import com.tangem.domain.managetokens.model.exceptoin.CustomTokenFormValidationException import com.tangem.domain.tokens.AddCryptoCurrenciesUseCase import com.tangem.domain.tokens.model.CryptoCurrency @@ -43,6 +44,7 @@ internal class CustomTokenFormModel @Inject constructor( private val customCurrencyValidator: CustomCurrencyValidator, private val addCryptoCurrenciesUseCase: AddCryptoCurrenciesUseCase, private val derivePublicKeysUseCase: DerivePublicKeysUseCase, + private val hasMissedDerivationsUseCase: HasMissedDerivationsUseCase, private val messageSender: UiMessageSender, private val customTokenFormManager: CustomCurrencyFormBuilder, private val analyticsEventHandler: AnalyticsEventHandler, @@ -156,7 +158,12 @@ internal class CustomTokenFormModel @Inject constructor( fillForm: Boolean, isAlreadyAdded: Boolean, isCustom: Boolean, - ) { + ) = modelScope.launch { + val needToAddDerivation = hasMissedDerivationsUseCase( + userWalletId = params.userWalletId, + networksWithDerivationPath = mapOf(currency.network.backendId to getDerivationPath().value), + ) + state.update { state -> var updatedState = state .updateWithProgress( @@ -166,6 +173,7 @@ internal class CustomTokenFormModel @Inject constructor( clearNotifications = true, clearFieldErrors = true, disableSecondaryFields = !isCustom, + needToAddDerivation = needToAddDerivation, ) if (fillForm) { diff --git a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/ui/CustomTokenFormContent.kt b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/ui/CustomTokenFormContent.kt index 5f5798395b..de79186792 100644 --- a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/ui/CustomTokenFormContent.kt +++ b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/ui/CustomTokenFormContent.kt @@ -22,9 +22,11 @@ import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.PreviewParameterProvider import androidx.compose.ui.unit.dp import androidx.compose.ui.util.fastForEach -import com.tangem.core.ui.components.PrimaryButton import com.tangem.core.ui.components.block.information.InformationBlock import com.tangem.core.ui.components.bottomFade +import com.tangem.core.ui.components.buttons.common.TangemButton +import com.tangem.core.ui.components.buttons.common.TangemButtonIconPosition +import com.tangem.core.ui.components.buttons.common.TangemButtonsDefaults import com.tangem.core.ui.components.fields.SimpleTextField import com.tangem.core.ui.components.isOpened import com.tangem.core.ui.components.keyboardAsState @@ -82,14 +84,21 @@ internal fun CustomTokenFormContent(model: CustomTokenFormUM, modifier: Modifier } } - PrimaryButton( + TangemButton( modifier = Modifier .align(Alignment.BottomCenter) .padding(bottom = TangemTheme.dimens.spacing16 + bottomBarHeight) .fillMaxWidth(), text = stringResource(id = R.string.custom_token_add_token), + colors = TangemButtonsDefaults.primaryButtonColors, enabled = model.canAddToken, showProgress = model.isValidating, + animateContentChange = true, + icon = if (model.needToAddDerivation) { + TangemButtonIconPosition.End(R.drawable.ic_tangem_24) + } else { + TangemButtonIconPosition.None + }, onClick = model.saveToken, ) } diff --git a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/utils/ui/CustomCurrencyFormOperations.kt b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/utils/ui/CustomCurrencyFormOperations.kt index d99bb9473b..147642b2bf 100644 --- a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/utils/ui/CustomCurrencyFormOperations.kt +++ b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/utils/ui/CustomCurrencyFormOperations.kt @@ -27,6 +27,7 @@ internal fun CustomTokenFormUM.updateWithProgress( showProgress: Boolean, isWasFilled: Boolean = this.tokenForm?.wasFilled ?: false, canAddToken: Boolean = this.canAddToken, + needToAddDerivation: Boolean = false, clearNotifications: Boolean = false, clearFieldErrors: Boolean = false, disableSecondaryFields: Boolean = false, @@ -34,6 +35,7 @@ internal fun CustomTokenFormUM.updateWithProgress( return copy( isValidating = showProgress, canAddToken = canAddToken, + needToAddDerivation = needToAddDerivation, notifications = if (clearNotifications) persistentListOf() else notifications, ).updateTokenForm { val updatedFields = fields.mapValues { (key, field) -> From f1a23ce0472b427596e83da14bda306f97c527be Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 22 Oct 2024 13:01:54 +0400 Subject: [PATCH 015/106] Updated on 2026-08-14 --- .../data/staking/DefaultStakingRepository.kt | 2 ++ .../intents/WalletWarningsClickIntents.kt | 15 ++++++++------- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/data/staking/src/main/java/com/tangem/data/staking/DefaultStakingRepository.kt b/data/staking/src/main/java/com/tangem/data/staking/DefaultStakingRepository.kt index bbf7601d27..2ee25c9a24 100644 --- a/data/staking/src/main/java/com/tangem/data/staking/DefaultStakingRepository.kt +++ b/data/staking/src/main/java/com/tangem/data/staking/DefaultStakingRepository.kt @@ -409,6 +409,8 @@ internal class DefaultStakingRepository( Timber.i("No yield balances available for $userWalletId") stakingBalanceStore.store(userWalletId, emptySet()) + cacheRegistry.invalidate(getYieldBalancesKey(userWalletId)) + return@invokeOnExpire } diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletWarningsClickIntents.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletWarningsClickIntents.kt index fb53cabe3d..2db549db07 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletWarningsClickIntents.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletWarningsClickIntents.kt @@ -131,13 +131,14 @@ internal class WalletWarningsClickIntentsImplementor @Inject constructor( derivePublicKeysUseCase( userWalletId = userWallet.walletId, currencies = missedAddressCurrencies, - ).onLeft { - Timber.e(it, "Failed to derive public keys") - return@launch - } - - // Refresh must be set to true to ensure that yield balances are updated - fetchTokenListUseCase(userWallet.walletId, mode = RefreshMode.SKIP_CURRENCIES) + ).fold( + ifLeft = { Timber.e(it, "Failed to derive public keys") }, + ifRight = { + fetchTokenListUseCase(userWallet.walletId, mode = RefreshMode.SKIP_CURRENCIES).onLeft { + Timber.e("Unable to refresh token list: $it") + } + }, + ) } } From 5fba9250e815c1ec6f9a7c97207cdbf9e7cc0049 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 23 Oct 2024 15:54:21 +0400 Subject: [PATCH 016/106] Updated on 2026-08-14 --- .../tangem/features/managetokens/ui/CustomTokenFormContent.kt | 1 + .../com/tangem/features/managetokens/ui/ManageTokensScreen.kt | 1 + 2 files changed, 2 insertions(+) diff --git a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/ui/CustomTokenFormContent.kt b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/ui/CustomTokenFormContent.kt index de79186792..fca42a82dc 100644 --- a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/ui/CustomTokenFormContent.kt +++ b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/ui/CustomTokenFormContent.kt @@ -99,6 +99,7 @@ internal fun CustomTokenFormContent(model: CustomTokenFormUM, modifier: Modifier } else { TangemButtonIconPosition.None }, + textStyle = TangemTheme.typography.subtitle1, onClick = model.saveToken, ) } diff --git a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/ui/ManageTokensScreen.kt b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/ui/ManageTokensScreen.kt index 2d604c67ee..3a85636b3e 100644 --- a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/ui/ManageTokensScreen.kt +++ b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/ui/ManageTokensScreen.kt @@ -185,6 +185,7 @@ private fun SaveChangesButton( }, showProgress = showProgress, colors = TangemButtonsDefaults.primaryButtonColors, + textStyle = TangemTheme.typography.subtitle1, enabled = true, animateContentChange = true, onClick = onClick, From ff5d3cb8b7bb3ba5511c5cbe04b2c02d09593d31 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 23 Oct 2024 14:32:20 +0400 Subject: [PATCH 017/106] Updated on 2026-08-14 --- .../tokenlist/entity/TokenListUM.kt | 35 ++++++++++++ .../tokenlist/entity/TokenListUMController.kt | 54 +++++++++++++++++++ .../entity/TokenListUMTransformer.kt | 10 ++++ .../transformer/SearchBarUMTransformer.kt | 33 ++++++++++++ .../UpdateSearchBarActiveStateTransformer.kt | 19 +++++++ .../UpdateSearchBarIntentsTransformer.kt | 13 +++++ .../UpdateSearchQueryTransformer.kt | 10 ++++ .../UpdateTokenItemsTransformer.kt | 28 ++++++++++ .../state/model/WalletTokensListState.kt | 6 +++ .../multicurrency/MultiCurrencyContentItem.kt | 9 ++++ 10 files changed, 217 insertions(+) create mode 100644 features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/tokenlist/entity/TokenListUM.kt create mode 100644 features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/tokenlist/entity/TokenListUMController.kt create mode 100644 features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/tokenlist/entity/TokenListUMTransformer.kt create mode 100644 features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/tokenlist/entity/transformer/SearchBarUMTransformer.kt create mode 100644 features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/tokenlist/entity/transformer/UpdateSearchBarActiveStateTransformer.kt create mode 100644 features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/tokenlist/entity/transformer/UpdateSearchBarIntentsTransformer.kt create mode 100644 features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/tokenlist/entity/transformer/UpdateSearchQueryTransformer.kt create mode 100644 features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/tokenlist/entity/transformer/UpdateTokenItemsTransformer.kt diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/tokenlist/entity/TokenListUM.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/tokenlist/entity/TokenListUM.kt new file mode 100644 index 0000000000..83d5fc1640 --- /dev/null +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/tokenlist/entity/TokenListUM.kt @@ -0,0 +1,35 @@ +package com.tangem.feature.wallet.presentation.tokenlist.entity + +import com.tangem.feature.wallet.presentation.wallet.state.model.WalletTokensListState.TokensListItemState +import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.persistentListOf + +/** + * Token list UM + * + * @property items items (search bar, tokens, headers) + * @property isBalanceHidden flag that indicates if balance should be hidden + * +[REDACTED_AUTHOR] + */ +internal data class TokenListUM( + val items: ImmutableList, + val isBalanceHidden: Boolean, +) { + + /** Get search bar if it exists */ + fun getSearchBar(): TokensListItemState.SearchBar? { + return items.firstOrNull() as? TokensListItemState.SearchBar + } + + /** Get tokens */ + fun getTokens(): ImmutableList { + if (getSearchBar() == null) return items + + return if (items.size > 1) { + items.subList(fromIndex = 1, toIndex = items.size) + } else { + persistentListOf() + } + } +} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/tokenlist/entity/TokenListUMController.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/tokenlist/entity/TokenListUMController.kt new file mode 100644 index 0000000000..6e3f7a5c6a --- /dev/null +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/tokenlist/entity/TokenListUMController.kt @@ -0,0 +1,54 @@ +package com.tangem.feature.wallet.presentation.tokenlist.entity + +import com.tangem.core.ui.components.fields.entity.SearchBarUM +import com.tangem.core.ui.extensions.resourceReference +import com.tangem.feature.wallet.impl.R +import com.tangem.feature.wallet.presentation.wallet.state.model.WalletTokensListState.TokensListItemState +import kotlinx.collections.immutable.persistentListOf +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.update +import timber.log.Timber +import javax.inject.Inject + +/** + * [TokenListUM] controller + * +[REDACTED_AUTHOR] + */ +internal class TokenListUMController @Inject constructor() { + + val state: StateFlow get() = _state + + private val _state: MutableStateFlow = MutableStateFlow( + value = TokenListUM( + items = persistentListOf(createInitialSearchBar()), + isBalanceHidden = false, + ), + ) + + fun update(transform: (TokenListUM) -> TokenListUM) { + Timber.d("Applying non-name transformation") + _state.update(transform) + } + + fun update(transformer: TokenListUMTransformer) { + Timber.d("Applying ${transformer::class.simpleName}") + _state.update(transformer::transform) + } + + /** Get search bar if it exists */ + fun getSearchBar(): TokensListItemState.SearchBar? = _state.value.getSearchBar() + + private fun createInitialSearchBar(): TokensListItemState.SearchBar { + return TokensListItemState.SearchBar( + searchBarUM = SearchBarUM( + placeholderText = resourceReference(id = R.string.common_search), + query = "", + onQueryChange = {}, + isActive = false, + onActiveChange = {}, + ), + ) + } +} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/tokenlist/entity/TokenListUMTransformer.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/tokenlist/entity/TokenListUMTransformer.kt new file mode 100644 index 0000000000..49678c022f --- /dev/null +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/tokenlist/entity/TokenListUMTransformer.kt @@ -0,0 +1,10 @@ +package com.tangem.feature.wallet.presentation.tokenlist.entity + +import com.tangem.utils.transformer.Transformer + +/** + * Base [TokenListUM] transformer + * +[REDACTED_AUTHOR] + */ +internal interface TokenListUMTransformer : Transformer \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/tokenlist/entity/transformer/SearchBarUMTransformer.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/tokenlist/entity/transformer/SearchBarUMTransformer.kt new file mode 100644 index 0000000000..a177307b2c --- /dev/null +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/tokenlist/entity/transformer/SearchBarUMTransformer.kt @@ -0,0 +1,33 @@ +package com.tangem.feature.wallet.presentation.tokenlist.entity.transformer + +import com.tangem.core.ui.components.fields.entity.SearchBarUM +import com.tangem.feature.wallet.presentation.tokenlist.entity.TokenListUM +import com.tangem.feature.wallet.presentation.tokenlist.entity.TokenListUMTransformer +import kotlinx.collections.immutable.persistentListOf + +/** + * Base [SearchBarUM] transformer + * +[REDACTED_AUTHOR] + */ +internal abstract class SearchBarUMTransformer : TokenListUMTransformer { + + abstract fun transform(prevState: SearchBarUM): SearchBarUM + + override fun transform(prevState: TokenListUM): TokenListUM { + val searchBarItem = prevState.getSearchBar() + + return if (searchBarItem != null) { + val updatedSearchBar = searchBarItem.copy(searchBarUM = transform(searchBarItem.searchBarUM)) + + prevState.copy( + items = persistentListOf( + updatedSearchBar, + *prevState.getTokens().toTypedArray(), + ), + ) + } else { + prevState + } + } +} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/tokenlist/entity/transformer/UpdateSearchBarActiveStateTransformer.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/tokenlist/entity/transformer/UpdateSearchBarActiveStateTransformer.kt new file mode 100644 index 0000000000..f33d3ea4ee --- /dev/null +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/tokenlist/entity/transformer/UpdateSearchBarActiveStateTransformer.kt @@ -0,0 +1,19 @@ +package com.tangem.feature.wallet.presentation.tokenlist.entity.transformer + +import com.tangem.core.ui.components.fields.entity.SearchBarUM +import com.tangem.core.ui.extensions.TextReference +import com.tangem.core.ui.extensions.resourceReference +import com.tangem.feature.wallet.impl.R + +internal class UpdateSearchBarActiveStateTransformer(private val isActive: Boolean) : SearchBarUMTransformer() { + + override fun transform(prevState: SearchBarUM): SearchBarUM { + val placeholderText = if (isActive) { + TextReference.EMPTY + } else { + resourceReference(id = R.string.common_search) + } + + return prevState.copy(placeholderText = placeholderText, isActive = isActive) + } +} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/tokenlist/entity/transformer/UpdateSearchBarIntentsTransformer.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/tokenlist/entity/transformer/UpdateSearchBarIntentsTransformer.kt new file mode 100644 index 0000000000..de58815cd1 --- /dev/null +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/tokenlist/entity/transformer/UpdateSearchBarIntentsTransformer.kt @@ -0,0 +1,13 @@ +package com.tangem.feature.wallet.presentation.tokenlist.entity.transformer + +import com.tangem.core.ui.components.fields.entity.SearchBarUM + +internal class UpdateSearchBarIntentsTransformer( + private val onQueryChange: (String) -> Unit, + private val onActiveChange: (Boolean) -> Unit, +) : SearchBarUMTransformer() { + + override fun transform(prevState: SearchBarUM): SearchBarUM { + return prevState.copy(onQueryChange = onQueryChange, onActiveChange = onActiveChange) + } +} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/tokenlist/entity/transformer/UpdateSearchQueryTransformer.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/tokenlist/entity/transformer/UpdateSearchQueryTransformer.kt new file mode 100644 index 0000000000..93a6a20675 --- /dev/null +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/tokenlist/entity/transformer/UpdateSearchQueryTransformer.kt @@ -0,0 +1,10 @@ +package com.tangem.feature.wallet.presentation.tokenlist.entity.transformer + +import com.tangem.core.ui.components.fields.entity.SearchBarUM + +internal class UpdateSearchQueryTransformer(private val newQuery: String) : SearchBarUMTransformer() { + + override fun transform(prevState: SearchBarUM): SearchBarUM { + return prevState.copy(query = newQuery) + } +} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/tokenlist/entity/transformer/UpdateTokenItemsTransformer.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/tokenlist/entity/transformer/UpdateTokenItemsTransformer.kt new file mode 100644 index 0000000000..89d171d3f9 --- /dev/null +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/tokenlist/entity/transformer/UpdateTokenItemsTransformer.kt @@ -0,0 +1,28 @@ +package com.tangem.feature.wallet.presentation.tokenlist.entity.transformer + +import com.tangem.common.ui.tokens.TokenItemStateConverter +import com.tangem.domain.appcurrency.model.AppCurrency +import com.tangem.domain.tokens.model.CryptoCurrencyStatus +import com.tangem.feature.wallet.presentation.tokenlist.entity.TokenListUM +import com.tangem.feature.wallet.presentation.tokenlist.entity.TokenListUMTransformer +import com.tangem.feature.wallet.presentation.wallet.state.model.WalletTokensListState.TokensListItemState +import kotlinx.collections.immutable.toImmutableList + +internal class UpdateTokenItemsTransformer( + appCurrency: AppCurrency, + onItemClick: (CryptoCurrencyStatus) -> Unit, + private val statuses: List, + private val isBalanceHidden: Boolean, +) : TokenListUMTransformer { + + private val converter = TokenItemStateConverter(appCurrency = appCurrency, onItemClick = onItemClick) + + override fun transform(prevState: TokenListUM): TokenListUM { + val items = converter.convertList(input = statuses).map(TokensListItemState::Token) + + return prevState.copy( + items = (listOfNotNull(prevState.getSearchBar()) + items).toImmutableList(), + isBalanceHidden = isBalanceHidden, + ) + } +} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletTokensListState.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletTokensListState.kt index c582f8d8d8..29daa446b4 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletTokensListState.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletTokensListState.kt @@ -1,6 +1,7 @@ package com.tangem.feature.wallet.presentation.wallet.state.model import androidx.compose.runtime.Immutable +import com.tangem.core.ui.components.fields.entity.SearchBarUM import com.tangem.core.ui.components.token.state.TokenItemState import com.tangem.core.ui.extensions.TextReference import com.tangem.feature.wallet.impl.R @@ -43,6 +44,11 @@ internal sealed class WalletTokensListState { abstract val id: Any + data class SearchBar( + override val id: Any = "search_bar", + val searchBarUM: SearchBarUM, + ) : TokensListItemState() + data class NetworkGroupTitle(override val id: Int, val name: TextReference) : TokensListItemState() data class Token(val state: TokenItemState) : TokensListItemState() { diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/multicurrency/MultiCurrencyContentItem.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/multicurrency/MultiCurrencyContentItem.kt index 6c3ca94c12..0b8e4ea13b 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/multicurrency/MultiCurrencyContentItem.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/multicurrency/MultiCurrencyContentItem.kt @@ -1,8 +1,11 @@ package com.tangem.feature.wallet.presentation.wallet.ui.components.multicurrency import androidx.compose.foundation.background +import androidx.compose.foundation.layout.padding import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import com.tangem.core.ui.components.fields.SearchBar import com.tangem.core.ui.components.token.TokenItem import com.tangem.core.ui.extensions.resolveReference import com.tangem.core.ui.res.TangemTheme @@ -36,5 +39,11 @@ internal fun MultiCurrencyContentItem( modifier = modifierWithBackground, ) } + is TokensListItemState.SearchBar -> { + SearchBar( + state = state.searchBarUM, + modifier = modifierWithBackground.padding(all = 12.dp), + ) + } } } \ No newline at end of file From 7476cc4e984252772ebf224156de7d534987eb4c Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 23 Oct 2024 16:14:28 +0400 Subject: [PATCH 018/106] Updated on 2026-08-14 --- .../tangem/domain/tokens/model/TokenList.kt | 9 +++++++++ .../operations/TokenListSortingOperations.kt | 6 +----- .../utils/TokenListAnalyticsSender.kt | 9 +-------- .../domain/GetMultiWalletWarningsFactory.kt | 18 +++--------------- .../wallet/domain/WalletWithFundsChecker.kt | 11 +---------- .../subscribers/BasicTokenListSubscriber.kt | 18 ++++++++---------- .../MultiWalletTokenListSubscriber.kt | 8 +------- 7 files changed, 24 insertions(+), 55 deletions(-) diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/model/TokenList.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/model/TokenList.kt index 88c7af38bd..f5eaf0b0e3 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/model/TokenList.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/model/TokenList.kt @@ -54,4 +54,13 @@ sealed class TokenList { enum class SortType { NONE, BALANCE, } + + /** Get flatten list of cryptocurrency status [CryptoCurrencyStatus] */ + fun flattenCurrencies(): List { + return when (this) { + is GroupedByNetwork -> groups.flatMap(NetworkGroup::currencies) + is Ungrouped -> currencies + is Empty -> emptyList() + } + } } \ No newline at end of file diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/TokenListSortingOperations.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/TokenListSortingOperations.kt index 7e4bc12ae2..6e27d5c2d7 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/TokenListSortingOperations.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/TokenListSortingOperations.kt @@ -23,11 +23,7 @@ internal class TokenListSortingOperations( sortByBalance: Boolean = tokenList.sortedBy == TokenList.SortType.BALANCE, isAnyTokenLoading: Boolean = tokenList.totalFiatBalance is TotalFiatBalance.Loading, ) : this( - currencies = when (tokenList) { - is TokenList.GroupedByNetwork -> tokenList.groups.flatMap { it.currencies } - is TokenList.Ungrouped -> tokenList.currencies - is TokenList.Empty -> emptyList() - }, + currencies = tokenList.flattenCurrencies(), isAnyTokenLoading = isAnyTokenLoading, sortByBalance = sortByBalance, ) diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/analytics/utils/TokenListAnalyticsSender.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/analytics/utils/TokenListAnalyticsSender.kt index 5d9840556b..28dfac6367 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/analytics/utils/TokenListAnalyticsSender.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/analytics/utils/TokenListAnalyticsSender.kt @@ -9,7 +9,6 @@ import com.tangem.core.analytics.models.AnalyticsParam import com.tangem.domain.analytics.CheckIsWalletToppedUpUseCase import com.tangem.domain.analytics.model.WalletBalanceState import com.tangem.domain.tokens.model.CryptoCurrencyStatus -import com.tangem.domain.tokens.model.NetworkGroup import com.tangem.domain.tokens.model.TokenList import com.tangem.domain.tokens.model.TotalFiatBalance import com.tangem.domain.wallets.models.UserWallet @@ -38,7 +37,7 @@ internal class TokenListAnalyticsSender @Inject constructor( if (displayedUiState == null || displayedUiState.pullToRefreshConfig.isRefreshing) return if (tokenList.totalFiatBalance is TotalFiatBalance.Loading) return - val currenciesStatuses = getCurrenciesStatuses(tokenList) + val currenciesStatuses = tokenList.flattenCurrencies() sendBalanceLoadedEventIfNeeded(tokenList.totalFiatBalance, currenciesStatuses) sendToppedUpEventIfNeeded(userWallet, tokenList.totalFiatBalance, currenciesStatuses) @@ -46,12 +45,6 @@ internal class TokenListAnalyticsSender @Inject constructor( sendTokenBalancesIfNeeded(currenciesStatuses) } - private fun getCurrenciesStatuses(tokenList: TokenList): List = when (tokenList) { - is TokenList.Empty -> emptyList() - is TokenList.GroupedByNetwork -> tokenList.groups.flatMap(NetworkGroup::currencies) - is TokenList.Ungrouped -> tokenList.currencies - } - private fun sendBalanceLoadedEventIfNeeded( fiatBalance: TotalFiatBalance, currenciesStatuses: List, diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/GetMultiWalletWarningsFactory.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/GetMultiWalletWarningsFactory.kt index 90b969933f..7178667b98 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/GetMultiWalletWarningsFactory.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/GetMultiWalletWarningsFactory.kt @@ -10,7 +10,6 @@ import com.tangem.domain.settings.ShouldShowRingPromoUseCase import com.tangem.domain.tokens.error.TokenListError import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.tokens.model.CryptoCurrencyStatus -import com.tangem.domain.tokens.model.NetworkGroup import com.tangem.domain.tokens.model.TokenList import com.tangem.domain.tokens.repository.PromoRepository import com.tangem.domain.wallets.models.UserWallet @@ -144,13 +143,8 @@ internal class GetMultiWalletWarningsFactory @Inject constructor( private fun Lce.getMissingAddressCurrencies(): List { val tokenList = getOrNull(isPartialContentAccepted = false) ?: return emptyList() - val currencies = when (tokenList) { - is TokenList.GroupedByNetwork -> tokenList.groups.flatMap(NetworkGroup::currencies) - is TokenList.Ungrouped -> tokenList.currencies - is TokenList.Empty -> emptyList() - } - - return currencies + return tokenList + .flattenCurrencies() .filter { it.value is CryptoCurrencyStatus.MissedDerivation } .map(CryptoCurrencyStatus::currency) } @@ -182,13 +176,7 @@ internal class GetMultiWalletWarningsFactory @Inject constructor( private fun Lce.hasUnreachableNetworks(): Boolean { val tokenList = getOrNull(isPartialContentAccepted = false) ?: return false - val currencies = when (tokenList) { - is TokenList.GroupedByNetwork -> tokenList.groups.flatMap(NetworkGroup::currencies) - is TokenList.Ungrouped -> tokenList.currencies - is TokenList.Empty -> emptyList() - } - - return currencies.any { it.value is CryptoCurrencyStatus.Unreachable } + return tokenList.flattenCurrencies().any { it.value is CryptoCurrencyStatus.Unreachable } } private fun MutableList.addRateTheAppNotification( diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/WalletWithFundsChecker.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/WalletWithFundsChecker.kt index bec1af9947..586234c6d6 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/WalletWithFundsChecker.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/WalletWithFundsChecker.kt @@ -3,7 +3,6 @@ package com.tangem.feature.wallet.presentation.wallet.domain import com.tangem.common.extensions.isZero import com.tangem.domain.settings.SetWalletWithFundsFoundUseCase import com.tangem.domain.tokens.model.CryptoCurrencyStatus -import com.tangem.domain.tokens.model.NetworkGroup import com.tangem.domain.tokens.model.TokenList import javax.inject.Inject @@ -12,15 +11,7 @@ internal class WalletWithFundsChecker @Inject constructor( ) { suspend fun check(tokenList: TokenList) { - val hasNonZeroWallets = when (tokenList) { - is TokenList.GroupedByNetwork -> { - tokenList.groups - .flatMap(NetworkGroup::currencies) - .hasNonZeroWallets() - } - is TokenList.Ungrouped -> tokenList.currencies.hasNonZeroWallets() - is TokenList.Empty -> false - } + val hasNonZeroWallets = tokenList.flattenCurrencies().hasNonZeroWallets() if (hasNonZeroWallets) setWalletWithFundsFoundUseCase() } diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/BasicTokenListSubscriber.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/BasicTokenListSubscriber.kt index 9c1bbbd713..a21e20afc9 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/BasicTokenListSubscriber.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/BasicTokenListSubscriber.kt @@ -8,7 +8,6 @@ import com.tangem.domain.core.lce.LceFlow import com.tangem.domain.core.utils.getOrElse import com.tangem.domain.tokens.RunPolkadotAccountHealthCheckUseCase import com.tangem.domain.tokens.error.TokenListError -import com.tangem.domain.tokens.model.NetworkGroup import com.tangem.domain.tokens.model.TokenList import com.tangem.domain.wallets.models.UserWallet import com.tangem.feature.wallet.presentation.wallet.analytics.utils.TokenListAnalyticsSender @@ -99,15 +98,14 @@ internal abstract class BasicTokenListSubscriber( private suspend fun startCheck(maybeTokenList: Lce) { // Run Polkadot account health check maybeTokenList.getOrNull()?.let { tokenList -> - val cryptoCurrencies = when (tokenList) { - is TokenList.GroupedByNetwork -> tokenList.groups.flatMap(NetworkGroup::currencies) - is TokenList.Ungrouped -> tokenList.currencies - is TokenList.Empty -> emptyList() - } - - cryptoCurrencies.forEach { - runPolkadotAccountHealthCheckUseCase(userWallet.walletId, it.currency.network) - } + tokenList + .flattenCurrencies() + .forEach { + runPolkadotAccountHealthCheckUseCase( + userWalletId = userWallet.walletId, + network = it.currency.network, + ) + } } } diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/MultiWalletTokenListSubscriber.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/MultiWalletTokenListSubscriber.kt index 79cef234af..a1fb33dfd0 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/MultiWalletTokenListSubscriber.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/MultiWalletTokenListSubscriber.kt @@ -70,12 +70,6 @@ internal class MultiWalletTokenListSubscriber( } private fun getCurrenciesIds(tokenList: TokenList): List { - return when (tokenList) { - is TokenList.GroupedByNetwork -> tokenList.groups.flatMap { group -> - group.currencies.map { it.currency.id } - } - is TokenList.Ungrouped -> tokenList.currencies.map { it.currency.id } - is TokenList.Empty -> emptyList() - } + return tokenList.flattenCurrencies().map { it.currency.id } } } \ No newline at end of file From 1ab4cdd170be92b72bf2518f94bd04e37f4fef31 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 24 Oct 2024 09:27:57 +0300 Subject: [PATCH 019/106] Updated on 2026-08-14 --- .../app/TangemWcBlockchainHelper.kt | 35 ++++++++++++------- .../domain/WalletConnectInteractor.kt | 18 ++++------ .../domain/WcBlockchainHelper.kt | 6 +++- .../walletconnect2/domain/WcJrpcMethods.kt | 4 +-- .../domain/WcSessionRequestConverter.kt | 20 +++++++++-- .../models/solana/SolanaTransactionRequest.kt | 5 ++- .../walletconnect/WalletConnectMiddleware.kt | 17 ++++----- .../configs/feature_toggles_config.json | 2 +- .../DefaultBlockchainSDKFactory.kt | 3 +- version.properties | 2 +- 10 files changed, 69 insertions(+), 43 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect2/app/TangemWcBlockchainHelper.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect2/app/TangemWcBlockchainHelper.kt index edddd1a886..8ed86f9fa2 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletconnect2/app/TangemWcBlockchainHelper.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletconnect2/app/TangemWcBlockchainHelper.kt @@ -4,6 +4,7 @@ import com.tangem.blockchain.common.Blockchain import com.tangem.blockchainsdk.utils.fromNetworkId import com.tangem.blockchainsdk.utils.toNetworkId import com.tangem.tap.domain.walletconnect2.domain.WcBlockchainHelper +import com.tangem.tap.domain.walletconnect2.domain.models.Account import com.tangem.tap.domain.walletconnect2.toggles.WalletConnectFeatureToggles internal class TangemWcBlockchainHelper( @@ -35,11 +36,12 @@ internal class TangemWcBlockchainHelper( } } - override fun networkIdToChainIdOrNull(networkId: String): String? { + override fun networkIdToChainIdOrNull(networkId: String): List { val blockchain = Blockchain.fromNetworkId(networkId) - val namespace = blockchain?.getCaip2Namespace() ?: return null - val chainId = blockchain.getCaip2ChainId() ?: return null - return "$namespace$CHAIN_SEPARATOR$chainId" + val namespace = blockchain?.getCaip2Namespace() ?: return emptyList() + return blockchain.getCaip2ChainIds().map { + "$namespace$CHAIN_SEPARATOR$it" + } } override fun getNamespaceFromFullChainIdOrNull(chainId: String): String? { @@ -52,8 +54,18 @@ internal class TangemWcBlockchainHelper( return Blockchain.fromNetworkId(networkId)?.fullName } - private fun Blockchain.getCaip2ChainId(): String? { - if (this.isEvm()) return this.getChainId()?.toString() + override fun chainIdsToAccounts( + walletAddress: String, + chainIds: List, + derivationPath: String?, + ): List { + return chainIds.map { chainId -> + Account(chainId, walletAddress, derivationPath) + } + } + + private fun Blockchain.getCaip2ChainIds(): List { + if (this.isEvm()) return listOfNotNull(this.getChainId()?.toString()) return when (this) { /* @@ -61,13 +73,12 @@ internal class TangemWcBlockchainHelper( * uncommented is used. * Docs: https://docs.walletconnect.com/advanced/multichain/chain-list * - * Blockchain.Solana -> "5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp" * */ - Blockchain.Solana -> "4sGjMW1sUnHzSxGspuhpqLDx6wiyjNtZ" - Blockchain.SolanaTestnet -> "z4uhcVJyU9pJkvQyS88uRDiswHXSCkY3z" - Blockchain.Polkadot -> "91b171bb158e2d3848fa23a9f1c25182" - Blockchain.Tron -> "0x2b6653dc" - else -> null + Blockchain.Solana -> listOf("5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp", "4sGjMW1sUnHzSxGspuhpqLDx6wiyjNtZ") + Blockchain.SolanaTestnet -> listOf("z4uhcVJyU9pJkvQyS88uRDiswHXSCkY3z") + Blockchain.Polkadot -> listOf("91b171bb158e2d3848fa23a9f1c25182") + Blockchain.Tron -> listOf("0x2b6653dc") + else -> emptyList() } } diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/WalletConnectInteractor.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/WalletConnectInteractor.kt index 122a9ed76f..9d3e956799 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/WalletConnectInteractor.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/WalletConnectInteractor.kt @@ -102,7 +102,7 @@ class WalletConnectInteractor( private suspend fun setupUserChains(userWallet: UserWallet, currencies: List) { val accounts = getAccountsForWc( userWallet = userWallet, - networks = currencies.map { it.network }, + networks = currencies.map { it.network }.distinct(), ) setUserChains(accounts) handleDeeplinkStack(accounts) @@ -403,18 +403,14 @@ class WalletConnectInteractor( derivationPath = it.derivationPath.value, ) } - return walletManagers.mapNotNull { + return walletManagers.flatMap { val wallet = it.wallet - val chainId = blockchainHelper.networkIdToChainIdOrNull( - wallet.blockchain.toNetworkId(), + val chainIds = blockchainHelper.networkIdToChainIdOrNull(wallet.blockchain.toNetworkId()) + blockchainHelper.chainIdsToAccounts( + walletAddress = wallet.address, + chainIds = chainIds, + derivationPath = wallet.publicKey.derivationPath?.rawPath, ) - chainId?.let { - Account( - chainId, - wallet.address, - wallet.publicKey.derivationPath?.rawPath, - ) - } } } diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/WcBlockchainHelper.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/WcBlockchainHelper.kt index 489d086af0..111c5c3d6f 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/WcBlockchainHelper.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/WcBlockchainHelper.kt @@ -1,13 +1,17 @@ package com.tangem.tap.domain.walletconnect2.domain +import com.tangem.tap.domain.walletconnect2.domain.models.Account + interface WcBlockchainHelper { fun chainIdToNetworkIdOrNull(chainId: String): String? fun chainIdToMissingNetworkNameOrNull(chainId: String): String? - fun networkIdToChainIdOrNull(networkId: String): String? + fun networkIdToChainIdOrNull(networkId: String): List fun getNamespaceFromFullChainIdOrNull(chainId: String): String? fun chainIdToFullNameOrNull(chainId: String): String? + + fun chainIdsToAccounts(walletAddress: String, chainIds: List, derivationPath: String?): List } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/WcJrpcMethods.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/WcJrpcMethods.kt index 3cb1187509..7c937b3772 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/WcJrpcMethods.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/WcJrpcMethods.kt @@ -223,14 +223,14 @@ internal class WcJrpcRequestsDeserializer @Inject constructor(@SdkMoshi private WcRequest.AddChain(data = deserializedParams) } WcJrpcMethods.SOLANA_SIGN_TX -> { - val tx = moshi.adapter(SolanaTransactionRequest::class.java) + val tx = moshi.adapter(SolanaTransactionRequest::class.java) .fromJsonOrNull(params) ?: return customRequest WcRequest.SolanaSignRequest(data = tx) } WcJrpcMethods.SOLANA_SIGN_MESSAGE -> { - val signMessage = moshi.adapter(SolanaSignMessage::class.java) + val signMessage = moshi.adapter(SolanaSignMessage::class.java) .fromJsonOrNull(params) ?: return customRequest val data = WcSignMessage( diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/WcSessionRequestConverter.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/WcSessionRequestConverter.kt index e02d7cc679..367d7b6a9e 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/WcSessionRequestConverter.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/WcSessionRequestConverter.kt @@ -1,12 +1,12 @@ package com.tangem.tap.domain.walletconnect2.domain import com.tangem.tap.domain.walletconnect.WalletConnectSdkHelper -import com.tangem.tap.domain.walletconnect2.domain.mapper.mapToTransaction import com.tangem.tap.domain.walletconnect2.domain.models.BnbData import com.tangem.tap.domain.walletconnect2.domain.models.EthTransactionData import com.tangem.tap.domain.walletconnect2.domain.models.WalletConnectError import com.tangem.tap.domain.walletconnect2.domain.models.WalletConnectEvents import com.tangem.tap.features.details.redux.walletconnect.WcEthTransactionType +import okio.ByteString.Companion.decodeBase64 internal class WcSessionRequestConverter( private val blockchainHelper: WcBlockchainHelper, @@ -115,11 +115,11 @@ internal class WcSessionRequestConverter( ) } is WcRequest.SolanaSignRequest -> { - val data = request.data.mapToTransaction() + val transaction = request.data.transaction WcPreparedRequest.SignTransaction( preparedRequestData = WcGenericTransactionData( - hashToSign = data.getSerializedMessage(), + hashToSign = transaction.prepareSolanaTransaction(), dAppName = sessionRequest.metaName, type = TransactionType.SOLANA_TX, ), @@ -132,6 +132,16 @@ internal class WcSessionRequestConverter( } } + /** + * Input transaction in Base64 string + */ + private fun String.prepareSolanaTransaction(): ByteArray { + return this.decodeBase64() + ?.toByteArray() + ?.drop(SOLANA_SIGNATURE_PLACEHOLDER_LENGTH) + ?.toByteArray() ?: ByteArray(0) + } + private fun getWalletAddress(request: WcRequest): String? { return when (request) { is WcRequest.BnbTrade -> request.data.accountNumber @@ -156,4 +166,8 @@ internal class WcSessionRequestConverter( it.chainId == sessionRequest.chainId && it.walletAddress.lowercase() == walletAddress?.lowercase() }?.derivationPath } + + companion object { + private const val SOLANA_SIGNATURE_PLACEHOLDER_LENGTH = 65 + } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/models/solana/SolanaTransactionRequest.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/models/solana/SolanaTransactionRequest.kt index 7ea66042f4..b77ecffae0 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/models/solana/SolanaTransactionRequest.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/models/solana/SolanaTransactionRequest.kt @@ -14,6 +14,9 @@ data class SolanaTransactionRequest( @Json(name = "instructions") val instructions: List, + + @Json(name = "transaction") + val transaction: String, ) : WcRequestData { @JsonClass(generateAdapter = true) @@ -22,7 +25,7 @@ data class SolanaTransactionRequest( val programId: String, @Json(name = "data") - val data: List, + val data: String, @Json(name = "keys") val keys: List, diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectMiddleware.kt b/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectMiddleware.kt index 6b0e0898ec..27df30674a 100644 --- a/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectMiddleware.kt @@ -13,7 +13,6 @@ import com.tangem.tap.common.redux.global.GlobalAction import com.tangem.tap.domain.walletconnect2.domain.LegacyWalletConnectRepository import com.tangem.tap.domain.walletconnect2.domain.WalletConnectInteractor import com.tangem.tap.domain.walletconnect2.domain.WcPreparedRequest -import com.tangem.tap.domain.walletconnect2.domain.models.Account import com.tangem.tap.domain.walletconnect2.domain.models.WalletConnectError import com.tangem.tap.features.demo.DemoHelper import com.tangem.tap.proxy.redux.DaggerGraphState @@ -87,18 +86,16 @@ class WalletConnectMiddleware { is WalletConnectAction.ApproveProposal -> { scope.launch { val accounts = getWalletManagers() - .mapNotNull { + .flatMap { val wallet = it.wallet - val chainId = walletConnectInteractor.blockchainHelper.networkIdToChainIdOrNull( + val chainIds = walletConnectInteractor.blockchainHelper.networkIdToChainIdOrNull( wallet.blockchain.toNetworkId(), ) - chainId?.let { - Account( - chainId, - wallet.address, - wallet.publicKey.derivationPath?.rawPath, - ) - } + walletConnectInteractor.blockchainHelper.chainIdsToAccounts( + walletAddress = wallet.address, + chainIds = chainIds, + derivationPath = wallet.publicKey.derivationPath?.rawPath, + ) } walletConnectInteractor.approveSessionProposal(accounts) } diff --git a/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json b/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json index 97119cabb4..b906e0ce74 100644 --- a/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json +++ b/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json @@ -5,7 +5,7 @@ }, { "name": "WC_SOLANA_TX_SIGN_ENABLED", - "version": "undefined" + "version": "5.18.0" }, { "name": "STAKING_ENABLED", diff --git a/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/DefaultBlockchainSDKFactory.kt b/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/DefaultBlockchainSDKFactory.kt index 9e5dbd53c8..741f900b6d 100644 --- a/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/DefaultBlockchainSDKFactory.kt +++ b/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/DefaultBlockchainSDKFactory.kt @@ -55,7 +55,8 @@ internal class DefaultBlockchainSDKFactory( // flow3 = subscribe on feature toggles changes, TODO: [REDACTED_JIRA] transform = walletManagerFactoryCreator::create, ) - .stateIn(scope = mainScope, started = SharingStarted.Lazily, initialValue = null) + // don't use Lazily because some features (WC) require initialized factory on app started + .stateIn(scope = mainScope, started = SharingStarted.Eagerly, initialValue = null) } private fun CoroutineScope.updateBlockchainProviderTypes() { diff --git a/version.properties b/version.properties index e7ffe36363..3a969ad8fe 100644 --- a/version.properties +++ b/version.properties @@ -1 +1 @@ -versionName=5.17.0 \ No newline at end of file +versionName=5.18.0 \ No newline at end of file From 50286e25581da3e70edbab1aa28997a512494cff Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 24 Oct 2024 13:50:31 +0300 Subject: [PATCH 020/106] Updated on 2026-08-14 --- .../impl/presentation/state/utils/StakingPendingActionUtils.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/utils/StakingPendingActionUtils.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/utils/StakingPendingActionUtils.kt index ae8f6d4cb2..2a8113944f 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/utils/StakingPendingActionUtils.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/utils/StakingPendingActionUtils.kt @@ -56,7 +56,7 @@ internal fun withStubUnstakeAction(networkId: String, activeStake: BalanceState) } internal fun isTronStakedBalance(networkId: String, pendingAction: PendingAction?): Boolean { - return isTron(networkId) && pendingAction?.type == StakingActionType.REVOKE + return isTron(networkId) && pendingAction?.type == StakingActionType.REVOTE } internal fun isComposePendingActions(networkId: String, pendingActions: ImmutableList?): Boolean { From 08023dffac3f89afc9450ec558368dbaec7f6272 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 25 Oct 2024 13:47:10 +0500 Subject: [PATCH 021/106] Updated on 2026-08-14 --- .../model/OnboardingManageTokensModel.kt | 15 ++++-- .../utils/list/ManageTokensListManager.kt | 1 + .../utils/list/ManageTokensUiManager.kt | 53 +++++++++++++------ 3 files changed, 47 insertions(+), 22 deletions(-) diff --git a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/model/OnboardingManageTokensModel.kt b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/model/OnboardingManageTokensModel.kt index 4c3c730ef6..6b4e801849 100644 --- a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/model/OnboardingManageTokensModel.kt +++ b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/model/OnboardingManageTokensModel.kt @@ -61,9 +61,11 @@ internal class OnboardingManageTokensModel @Inject constructor( .onEach { status -> updatePaginationStatus(status) } .launchIn(modelScope) - manageTokensListManager.currenciesToAdd - .onEach(::handleNewAddedCurrencies) - .launchIn(modelScope) + combine( + manageTokensListManager.currenciesToAdd, + manageTokensListManager.currenciesToRemove, + ::handleChangedCurrencies, + ).launchIn(modelScope) observeSearchQueryChanges() @@ -192,8 +194,11 @@ internal class OnboardingManageTokensModel @Inject constructor( state.update { state -> state.copy(scrollToTop = consumedEvent()) } } - private suspend fun handleNewAddedCurrencies(currenciesToAdd: ChangedCurrencies) { - if (currenciesToAdd.isEmpty()) { + private suspend fun handleChangedCurrencies( + currenciesToAdd: ChangedCurrencies, + currenciesToRemove: ChangedCurrencies, + ) { + if (currenciesToAdd.isEmpty() && currenciesToRemove.isEmpty()) { state.update { state -> state.copy( actionButtonConfig = OnboardingManageTokensUM.ActionButtonConfig.Later(onClick = ::onLaterClick), diff --git a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/utils/list/ManageTokensListManager.kt b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/utils/list/ManageTokensListManager.kt index 551debff20..414fd1161a 100644 --- a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/utils/list/ManageTokensListManager.kt +++ b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/utils/list/ManageTokensListManager.kt @@ -68,6 +68,7 @@ internal class ManageTokensListManager @Inject constructor( dispatchers = dispatchers, actions = this, scopeProvider = Provider { scope }, + sourceProvider = Provider { source }, clipboardManager = clipboardManager, ) diff --git a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/utils/list/ManageTokensUiManager.kt b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/utils/list/ManageTokensUiManager.kt index 167a7f88a7..8ed4841f87 100644 --- a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/utils/list/ManageTokensUiManager.kt +++ b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/utils/list/ManageTokensUiManager.kt @@ -10,6 +10,7 @@ import com.tangem.core.ui.message.SnackbarMessage import com.tangem.domain.managetokens.model.CurrencyUnsupportedState import com.tangem.domain.managetokens.model.ManagedCryptoCurrency import com.tangem.domain.tokens.model.Network +import com.tangem.features.managetokens.component.ManageTokensSource import com.tangem.features.managetokens.entity.item.CurrencyItemUM import com.tangem.features.managetokens.impl.R import com.tangem.features.managetokens.ui.dialog.CurrencyUnsupportedDialog @@ -29,11 +30,13 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.* import kotlinx.coroutines.launch +@Suppress("LongParameterList") internal class ManageTokensUiManager( private val state: MutableStateFlow, private val messageSender: UiMessageSender, private val dispatchers: CoroutineDispatcherProvider, private val scopeProvider: Provider, + private val sourceProvider: Provider, private val actions: ManageTokensUiActions, private val clipboardManager: ClipboardManager, ) { @@ -41,6 +44,9 @@ internal class ManageTokensUiManager( private val scope: CoroutineScope get() = scopeProvider() + private val source: ManageTokensSource + get() = sourceProvider() + @OptIn(ExperimentalCoroutinesApi::class) val items: Flow> = state .mapLatest { state -> @@ -225,26 +231,39 @@ internal class ManageTokensUiManager( } else { actions.checkHasLinkedTokens(userWalletId, network) } + val canHideWithoutConfirming = source == ManageTokensSource.ONBOARDING - val message = ContentMessage { onDismiss -> - if (hasLinkedTokens) { - HasLinkedTokensWarning( - currency = currency, - network = network, - onDismiss = onDismiss, - ) - } else { - HideTokenWarning( - currency = currency, - onConfirm = { - onConfirm() - onDismiss() - }, - onDismiss = onDismiss, - ) - } + if (hasLinkedTokens) { + showLinkedTokensWarning(currency, network) + } else if (canHideWithoutConfirming) { + onConfirm() + } else { + showHideTokenWarning(currency, onConfirm) } + } + private fun showLinkedTokensWarning(currency: ManagedCryptoCurrency, network: Network) { + val message = ContentMessage { onDismiss -> + HasLinkedTokensWarning( + currency = currency, + network = network, + onDismiss = onDismiss, + ) + } + messageSender.send(message) + } + + private fun showHideTokenWarning(currency: ManagedCryptoCurrency, onConfirm: () -> Unit) { + val message = ContentMessage { onDismiss -> + HideTokenWarning( + currency = currency, + onConfirm = { + onConfirm() + onDismiss() + }, + onDismiss = onDismiss, + ) + } messageSender.send(message) } From 321b026fb405dadf2efb322b0f4c49b62035cd88 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 24 Oct 2024 12:18:09 +0400 Subject: [PATCH 022/106] Updated on 2026-08-14 --- .../com/tangem/utils/coroutines/JobHolder.kt | 14 ++- .../tokenlist/DefaultTokenListComponent.kt | 35 ++++++ .../tokenlist/TokenListComponent.kt | 28 +++++ .../tokenlist/di/TokenListComponentModule.kt | 18 +++ .../tokenlist/di/TokenListModelModule.kt | 20 ++++ .../tokenlist/entity/TokenListUMController.kt | 19 +--- .../UpdateSearchBarIntentsTransformer.kt | 13 --- .../UpdateTokenItemsTransformer.kt | 34 ++++-- .../tokenlist/model/TokenListModel.kt | 105 ++++++++++++++++++ .../presentation/tokenlist/ui/TokenList.kt | 64 +++++++++++ .../ui/preview/PreviewTokenListUMProvider.kt | 62 +++++++++++ .../tokenlist/utils/SearchTokensManager.kt | 29 +++++ 12 files changed, 404 insertions(+), 37 deletions(-) create mode 100644 features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/tokenlist/DefaultTokenListComponent.kt create mode 100644 features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/tokenlist/TokenListComponent.kt create mode 100644 features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/tokenlist/di/TokenListComponentModule.kt create mode 100644 features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/tokenlist/di/TokenListModelModule.kt delete mode 100644 features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/tokenlist/entity/transformer/UpdateSearchBarIntentsTransformer.kt create mode 100644 features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/tokenlist/model/TokenListModel.kt create mode 100644 features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/tokenlist/ui/TokenList.kt create mode 100644 features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/tokenlist/ui/preview/PreviewTokenListUMProvider.kt create mode 100644 features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/tokenlist/utils/SearchTokensManager.kt diff --git a/core/utils/src/main/java/com/tangem/utils/coroutines/JobHolder.kt b/core/utils/src/main/java/com/tangem/utils/coroutines/JobHolder.kt index d50f9f295c..6e5a872f02 100644 --- a/core/utils/src/main/java/com/tangem/utils/coroutines/JobHolder.kt +++ b/core/utils/src/main/java/com/tangem/utils/coroutines/JobHolder.kt @@ -1,6 +1,9 @@ package com.tangem.utils.coroutines +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch /** * Job holder. It is automatically finished old job if new one is started @@ -26,4 +29,13 @@ class JobHolder { fun Job.saveIn(jobHolder: JobHolder): Job = jobHolder.update(job = this) -suspend fun Job.saveInAndJoin(jobHolder: JobHolder) = saveIn(jobHolder).join() \ No newline at end of file +suspend fun Job.saveInAndJoin(jobHolder: JobHolder) = saveIn(jobHolder).join() + +fun CoroutineScope.withDebounce(jobHolder: JobHolder, timeMillis: Long = 800L, function: () -> Unit) { + launch { + delay(timeMillis = timeMillis) + + function() + } + .saveIn(jobHolder) +} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/tokenlist/DefaultTokenListComponent.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/tokenlist/DefaultTokenListComponent.kt new file mode 100644 index 0000000000..c74231c846 --- /dev/null +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/tokenlist/DefaultTokenListComponent.kt @@ -0,0 +1,35 @@ +package com.tangem.feature.wallet.presentation.tokenlist + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.Stable +import androidx.compose.runtime.getValue +import androidx.compose.ui.Modifier +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.tangem.core.decompose.context.AppComponentContext +import com.tangem.core.decompose.model.getOrCreateModel +import com.tangem.feature.wallet.presentation.tokenlist.model.TokenListModel +import com.tangem.feature.wallet.presentation.tokenlist.ui.TokenList +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject + +@Stable +internal class DefaultTokenListComponent @AssistedInject constructor( + @Assisted context: AppComponentContext, + @Assisted params: TokenListComponent.Params, +) : TokenListComponent, AppComponentContext by context { + + private val model: TokenListModel = getOrCreateModel(params) + + @Composable + override fun Content(modifier: Modifier) { + val state by model.state.collectAsStateWithLifecycle() + + TokenList(state = state, modifier = modifier) + } + + @AssistedFactory + interface Factory : TokenListComponent.Factory { + override fun create(context: AppComponentContext, params: TokenListComponent.Params): DefaultTokenListComponent + } +} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/tokenlist/TokenListComponent.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/tokenlist/TokenListComponent.kt new file mode 100644 index 0000000000..3c271c0894 --- /dev/null +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/tokenlist/TokenListComponent.kt @@ -0,0 +1,28 @@ +package com.tangem.feature.wallet.presentation.tokenlist + +import androidx.compose.runtime.Stable +import com.tangem.core.decompose.factory.ComponentFactory +import com.tangem.core.ui.decompose.ComposableContentComponent +import com.tangem.domain.tokens.model.CryptoCurrencyStatus +import com.tangem.domain.wallets.models.UserWalletId + +/** Token list component that present list of token for multi-currency wallet */ +@Stable +internal interface TokenListComponent : ComposableContentComponent { + + /** Component factory */ + interface Factory : ComponentFactory + + /** + * Params + * + * @property hasSearchBar flag that indicates if search bar should be shown + * @property userWalletId id of multi-currency wallet + * @property onTokenClick callback for token click + */ + data class Params( + val hasSearchBar: Boolean, + val userWalletId: UserWalletId, + val onTokenClick: (CryptoCurrencyStatus) -> Unit, + ) +} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/tokenlist/di/TokenListComponentModule.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/tokenlist/di/TokenListComponentModule.kt new file mode 100644 index 0000000000..1e2672c2af --- /dev/null +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/tokenlist/di/TokenListComponentModule.kt @@ -0,0 +1,18 @@ +package com.tangem.feature.wallet.presentation.tokenlist.di + +import com.tangem.feature.wallet.presentation.tokenlist.DefaultTokenListComponent +import com.tangem.feature.wallet.presentation.tokenlist.TokenListComponent +import dagger.Binds +import dagger.Module +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +internal interface TokenListComponentModule { + + @Binds + @Singleton + fun bindTokenListComponentFactory(factory: DefaultTokenListComponent.Factory): TokenListComponent.Factory +} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/tokenlist/di/TokenListModelModule.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/tokenlist/di/TokenListModelModule.kt new file mode 100644 index 0000000000..732340f818 --- /dev/null +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/tokenlist/di/TokenListModelModule.kt @@ -0,0 +1,20 @@ +package com.tangem.feature.wallet.presentation.tokenlist.di + +import com.tangem.core.decompose.di.DecomposeComponent +import com.tangem.core.decompose.model.Model +import com.tangem.feature.wallet.presentation.tokenlist.model.TokenListModel +import dagger.Binds +import dagger.Module +import dagger.hilt.InstallIn +import dagger.multibindings.ClassKey +import dagger.multibindings.IntoMap + +@Module +@InstallIn(DecomposeComponent::class) +internal interface TokenListModelModule { + + @Binds + @IntoMap + @ClassKey(TokenListModel::class) + fun provideTokenListModel(model: TokenListModel): Model +} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/tokenlist/entity/TokenListUMController.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/tokenlist/entity/TokenListUMController.kt index 6e3f7a5c6a..04592b5ed0 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/tokenlist/entity/TokenListUMController.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/tokenlist/entity/TokenListUMController.kt @@ -1,8 +1,5 @@ package com.tangem.feature.wallet.presentation.tokenlist.entity -import com.tangem.core.ui.components.fields.entity.SearchBarUM -import com.tangem.core.ui.extensions.resourceReference -import com.tangem.feature.wallet.impl.R import com.tangem.feature.wallet.presentation.wallet.state.model.WalletTokensListState.TokensListItemState import kotlinx.collections.immutable.persistentListOf import kotlinx.coroutines.flow.MutableStateFlow @@ -22,7 +19,7 @@ internal class TokenListUMController @Inject constructor() { private val _state: MutableStateFlow = MutableStateFlow( value = TokenListUM( - items = persistentListOf(createInitialSearchBar()), + items = persistentListOf(), isBalanceHidden = false, ), ) @@ -38,17 +35,7 @@ internal class TokenListUMController @Inject constructor() { } /** Get search bar if it exists */ - fun getSearchBar(): TokensListItemState.SearchBar? = _state.value.getSearchBar() - - private fun createInitialSearchBar(): TokensListItemState.SearchBar { - return TokensListItemState.SearchBar( - searchBarUM = SearchBarUM( - placeholderText = resourceReference(id = R.string.common_search), - query = "", - onQueryChange = {}, - isActive = false, - onActiveChange = {}, - ), - ) + fun getSearchBar(): TokensListItemState.SearchBar? { + return _state.value.getSearchBar() } } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/tokenlist/entity/transformer/UpdateSearchBarIntentsTransformer.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/tokenlist/entity/transformer/UpdateSearchBarIntentsTransformer.kt deleted file mode 100644 index de58815cd1..0000000000 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/tokenlist/entity/transformer/UpdateSearchBarIntentsTransformer.kt +++ /dev/null @@ -1,13 +0,0 @@ -package com.tangem.feature.wallet.presentation.tokenlist.entity.transformer - -import com.tangem.core.ui.components.fields.entity.SearchBarUM - -internal class UpdateSearchBarIntentsTransformer( - private val onQueryChange: (String) -> Unit, - private val onActiveChange: (Boolean) -> Unit, -) : SearchBarUMTransformer() { - - override fun transform(prevState: SearchBarUM): SearchBarUM { - return prevState.copy(onQueryChange = onQueryChange, onActiveChange = onActiveChange) - } -} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/tokenlist/entity/transformer/UpdateTokenItemsTransformer.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/tokenlist/entity/transformer/UpdateTokenItemsTransformer.kt index 89d171d3f9..ed4dcf5c9e 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/tokenlist/entity/transformer/UpdateTokenItemsTransformer.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/tokenlist/entity/transformer/UpdateTokenItemsTransformer.kt @@ -1,28 +1,48 @@ package com.tangem.feature.wallet.presentation.tokenlist.entity.transformer import com.tangem.common.ui.tokens.TokenItemStateConverter -import com.tangem.domain.appcurrency.model.AppCurrency +import com.tangem.core.ui.components.fields.entity.SearchBarUM +import com.tangem.core.ui.extensions.resourceReference import com.tangem.domain.tokens.model.CryptoCurrencyStatus +import com.tangem.feature.wallet.impl.R import com.tangem.feature.wallet.presentation.tokenlist.entity.TokenListUM import com.tangem.feature.wallet.presentation.tokenlist.entity.TokenListUMTransformer import com.tangem.feature.wallet.presentation.wallet.state.model.WalletTokensListState.TokensListItemState import kotlinx.collections.immutable.toImmutableList internal class UpdateTokenItemsTransformer( - appCurrency: AppCurrency, - onItemClick: (CryptoCurrencyStatus) -> Unit, + private val tokenItemStateConverter: TokenItemStateConverter, private val statuses: List, private val isBalanceHidden: Boolean, + private val hasSearchBar: Boolean, + private val onQueryChange: (String) -> Unit, + private val onActiveChange: (Boolean) -> Unit, ) : TokenListUMTransformer { - private val converter = TokenItemStateConverter(appCurrency = appCurrency, onItemClick = onItemClick) - override fun transform(prevState: TokenListUM): TokenListUM { - val items = converter.convertList(input = statuses).map(TokensListItemState::Token) + val items = tokenItemStateConverter.convertList(input = statuses).map(TokensListItemState::Token) + + val searchBarItem = if (hasSearchBar) { + prevState.getSearchBar() ?: createSearchBarItem() + } else { + null + } return prevState.copy( - items = (listOfNotNull(prevState.getSearchBar()) + items).toImmutableList(), + items = (listOfNotNull(searchBarItem) + items).toImmutableList(), isBalanceHidden = isBalanceHidden, ) } + + private fun createSearchBarItem(): TokensListItemState.SearchBar { + return TokensListItemState.SearchBar( + searchBarUM = SearchBarUM( + placeholderText = resourceReference(id = R.string.common_search), + query = "", + onQueryChange = onQueryChange, + isActive = false, + onActiveChange = onActiveChange, + ), + ) + } } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/tokenlist/model/TokenListModel.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/tokenlist/model/TokenListModel.kt new file mode 100644 index 0000000000..fb13ef8d9d --- /dev/null +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/tokenlist/model/TokenListModel.kt @@ -0,0 +1,105 @@ +package com.tangem.feature.wallet.presentation.tokenlist.model + +import arrow.core.getOrElse +import com.tangem.common.ui.tokens.TokenItemStateConverter +import com.tangem.core.decompose.model.Model +import com.tangem.core.decompose.model.ParamsContainer +import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase +import com.tangem.domain.appcurrency.model.AppCurrency +import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase +import com.tangem.domain.core.utils.getOrElse +import com.tangem.domain.tokens.GetTokenListUseCase +import com.tangem.domain.tokens.model.CryptoCurrencyStatus +import com.tangem.domain.tokens.model.TokenList +import com.tangem.feature.wallet.presentation.tokenlist.TokenListComponent +import com.tangem.feature.wallet.presentation.tokenlist.entity.TokenListUM +import com.tangem.feature.wallet.presentation.tokenlist.entity.TokenListUMController +import com.tangem.feature.wallet.presentation.tokenlist.entity.transformer.UpdateSearchBarActiveStateTransformer +import com.tangem.feature.wallet.presentation.tokenlist.entity.transformer.UpdateSearchQueryTransformer +import com.tangem.feature.wallet.presentation.tokenlist.entity.transformer.UpdateTokenItemsTransformer +import com.tangem.feature.wallet.presentation.tokenlist.utils.SearchTokensManager +import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import kotlinx.coroutines.flow.* +import kotlinx.coroutines.launch +import javax.inject.Inject + +@Suppress("LongParameterList") +internal class TokenListModel @Inject constructor( + paramsContainer: ParamsContainer, + override val dispatchers: CoroutineDispatcherProvider, + private val tokenListUMController: TokenListUMController, + private val searchTokensManager: SearchTokensManager, + private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase, + private val getBalanceHidingSettingsUseCase: GetBalanceHidingSettingsUseCase, + private val getTokenListUseCase: GetTokenListUseCase, +) : Model() { + + val state: StateFlow = tokenListUMController.state + + private val params: TokenListComponent.Params = paramsContainer.require() + + init { + subscribeOnUpdateState() + } + + private fun subscribeOnUpdateState() { + combine( + flow = getTokenListUseCase.launch(userWalletId = params.userWalletId).distinctUntilChanged(), + flow2 = getSelectedAppCurrencyUseCase().map { it.getOrElse { AppCurrency.Default } }.distinctUntilChanged(), + flow3 = getBalanceHidingSettingsUseCase().map { it.isBalanceHidden }.distinctUntilChanged(), + flow4 = searchTokensManager.query, + ) { maybeTokenList, appCurrency, isBalanceHidden, query -> + val currencies = maybeTokenList.getOrElse( + ifLoading = { it ?: TokenList.Empty }, + ifError = { TokenList.Empty }, + ) + .flattenCurrencies() + + val filterTokenList = currencies + .filterByQuery(query = query) + .filterByAvailability() + + UpdateTokenItemsTransformer( + tokenItemStateConverter = TokenItemStateConverter( + appCurrency = appCurrency, + onItemClick = params.onTokenClick, + ), + statuses = filterTokenList, + isBalanceHidden = isBalanceHidden, + hasSearchBar = params.hasSearchBar && currencies.isNotEmpty(), + onQueryChange = ::onSearchQueryChange, + onActiveChange = ::onSearchBarActiveChange, + ) + } + .onEach(tokenListUMController::update) + .flowOn(dispatchers.main) + .launchIn(modelScope) + } + + private fun onSearchQueryChange(newQuery: String) { + val searchBar = tokenListUMController.getSearchBar() + if (searchBar?.searchBarUM?.query == newQuery) return + + modelScope.launch { + tokenListUMController.update(transformer = UpdateSearchQueryTransformer(newQuery)) + + searchTokensManager.update(newQuery) + } + } + + private fun onSearchBarActiveChange(isActive: Boolean) { + tokenListUMController.update(transformer = UpdateSearchBarActiveStateTransformer(isActive)) + } + + private fun List.filterByQuery(query: String): List { + return filter { + it.currency.name.contains(other = query, ignoreCase = true) || + it.currency.symbol.contains(other = query, ignoreCase = true) + } + } + + // TODO: [REDACTED_JIRA] + private fun List.filterByAvailability(): List { + return this + } +} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/tokenlist/ui/TokenList.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/tokenlist/ui/TokenList.kt new file mode 100644 index 0000000000..e780c03e44 --- /dev/null +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/tokenlist/ui/TokenList.kt @@ -0,0 +1,64 @@ +package com.tangem.feature.wallet.presentation.tokenlist.ui + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.itemsIndexed +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.tooling.preview.PreviewParameter +import androidx.compose.ui.unit.dp +import com.tangem.core.ui.decorations.roundedShapeItemDecoration +import com.tangem.core.ui.res.TangemTheme +import com.tangem.core.ui.res.TangemThemePreview +import com.tangem.feature.wallet.presentation.tokenlist.entity.TokenListUM +import com.tangem.feature.wallet.presentation.tokenlist.ui.preview.PreviewTokenListUMProvider +import com.tangem.feature.wallet.presentation.wallet.ui.components.multicurrency.MultiCurrencyContentItem + +/** + * Token list + * + * @param state state + * @param modifier modifier + * +[REDACTED_AUTHOR] + */ +@Composable +internal fun TokenList(state: TokenListUM, modifier: Modifier = Modifier) { + LazyColumn(modifier = modifier) { + itemsIndexed( + items = state.items, + key = { _, item -> item.id }, + contentType = { _, item -> item::class.java }, + itemContent = { index, item -> + MultiCurrencyContentItem( + state = item, + isBalanceHidden = state.isBalanceHidden, + modifier = Modifier + .animateItem() + .roundedShapeItemDecoration( + currentIndex = index, + lastIndex = state.items.lastIndex, + addDefaultPadding = false, + ), + ) + }, + ) + } +} + +@Preview +@Composable +private fun Preview_TokenList(@PreviewParameter(PreviewTokenListUMProvider::class) state: TokenListUM) { + TangemThemePreview { + TokenList( + state = state, + modifier = Modifier + .fillMaxWidth() + .background(color = TangemTheme.colors.background.secondary) + .padding(16.dp), + ) + } +} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/tokenlist/ui/preview/PreviewTokenListUMProvider.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/tokenlist/ui/preview/PreviewTokenListUMProvider.kt new file mode 100644 index 0000000000..4a0b5ad769 --- /dev/null +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/tokenlist/ui/preview/PreviewTokenListUMProvider.kt @@ -0,0 +1,62 @@ +package com.tangem.feature.wallet.presentation.tokenlist.ui.preview + +import androidx.compose.ui.tooling.preview.PreviewParameterProvider +import com.tangem.core.ui.components.currency.icon.CurrencyIconState +import com.tangem.core.ui.components.fields.entity.SearchBarUM +import com.tangem.core.ui.components.marketprice.PriceChangeType +import com.tangem.core.ui.components.token.state.TokenItemState +import com.tangem.core.ui.extensions.resourceReference +import com.tangem.feature.wallet.impl.R +import com.tangem.feature.wallet.presentation.tokenlist.entity.TokenListUM +import com.tangem.feature.wallet.presentation.wallet.state.model.WalletTokensListState.TokensListItemState +import kotlinx.collections.immutable.toImmutableList + +internal class PreviewTokenListUMProvider : PreviewParameterProvider { + + override val values: Sequence = sequenceOf( + createTokensList(hasSearchBar = false, createDefaultTokenItem()), + createTokensList(hasSearchBar = true, createDefaultTokenItem()), + ) + + private fun createTokensList(hasSearchBar: Boolean, vararg items: TokensListItemState): TokenListUM { + return TokenListUM( + items = buildList { + if (hasSearchBar) { + TokensListItemState.SearchBar( + searchBarUM = SearchBarUM( + placeholderText = resourceReference(id = R.string.common_search), + query = "", + onQueryChange = {}, + isActive = false, + onActiveChange = {}, + ), + ) + .let { add(element = it) } + } + + addAll(items) + } + .toImmutableList(), + isBalanceHidden = false, + ) + } + + private fun createDefaultTokenItem(): TokensListItemState.Token { + return TokensListItemState.Token( + state = TokenItemState.Content( + id = "1", + iconState = CurrencyIconState.Locked, + titleState = TokenItemState.TitleState.Content(text = "Bitcoin"), + fiatAmountState = TokenItemState.FiatAmountState.Content(text = "12 368,14 \$"), + subtitle2State = TokenItemState.Subtitle2State.TextContent(text = "0,35853044 BTC"), + subtitleState = TokenItemState.SubtitleState.CryptoPriceContent( + price = "34 496,75 \$", + priceChangePercent = "0,43 %", + type = PriceChangeType.DOWN, + ), + onItemClick = {}, + onItemLongClick = {}, + ), + ) + } +} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/tokenlist/utils/SearchTokensManager.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/tokenlist/utils/SearchTokensManager.kt new file mode 100644 index 0000000000..98258cca19 --- /dev/null +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/tokenlist/utils/SearchTokensManager.kt @@ -0,0 +1,29 @@ +package com.tangem.feature.wallet.presentation.tokenlist.utils + +import com.tangem.utils.coroutines.JobHolder +import com.tangem.utils.coroutines.withDebounce +import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow +import javax.inject.Inject + +/** + * Search tokens manager + * +[REDACTED_AUTHOR] + */ +internal class SearchTokensManager @Inject constructor() { + + val query: Flow + get() = _query + + private val _query = MutableStateFlow(value = "") + + private val jobHolder = JobHolder() + + suspend fun update(value: String) { + coroutineScope { + withDebounce(jobHolder) { _query.value = value } + } + } +} \ No newline at end of file From 5ae666646f09363df15e326ef5c3fd529296e72d Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 25 Oct 2024 14:18:36 +0500 Subject: [PATCH 023/106] Updated on 2026-08-14 --- .../middlewares/TradeCryptoMiddleware.kt | 9 + .../tangem/tap/routing/utils/ChildFactory.kt | 9 + .../com/tangem/common/routing/AppRoute.kt | 3 + .../entity/item/CurrencyNetworkUM.kt | 2 - .../ui/CustomTokenSelectorContent.kt | 12 ++ .../onramp/component/OnrampComponent.kt | 4 +- features/onramp/impl/build.gradle.kts | 9 +- .../component/ConfirmResidencyComponent.kt | 16 ++ .../impl/DefaultConfirmResidencyComponent.kt | 55 ++++++ .../component/impl/DefaultOnrampComponent.kt | 64 +++++++ .../PreviewConfirmResidencyComponent.kt | 46 +++++ .../onramp/di/OnrampComponentModule.kt | 26 +++ ...eatureModule.kt => OnrampFeatureModule.kt} | 2 +- .../features/onramp/di/OnrampModelModule.kt | 26 +++ .../onramp/entity/ConfirmResidencyUM.kt | 16 ++ .../onramp/entity/OnrampBottomSheetConfig.kt | 10 ++ .../onramp/model/ConfirmResidencyModel.kt | 53 ++++++ .../features/onramp/model/OnrampModel.kt | 38 ++++ .../onramp/ui/ResidenceBottomSheet.kt | 165 ++++++++++++++++++ 19 files changed, 559 insertions(+), 6 deletions(-) create mode 100644 features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/component/ConfirmResidencyComponent.kt create mode 100644 features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/component/impl/DefaultConfirmResidencyComponent.kt create mode 100644 features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/component/impl/DefaultOnrampComponent.kt create mode 100644 features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/component/preview/PreviewConfirmResidencyComponent.kt create mode 100644 features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/di/OnrampComponentModule.kt rename features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/di/{FeatureModule.kt => OnrampFeatureModule.kt} (94%) create mode 100644 features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/di/OnrampModelModule.kt create mode 100644 features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/entity/ConfirmResidencyUM.kt create mode 100644 features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/entity/OnrampBottomSheetConfig.kt create mode 100644 features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/model/ConfirmResidencyModel.kt create mode 100644 features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/model/OnrampModel.kt create mode 100644 features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/ui/ResidenceBottomSheet.kt diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/TradeCryptoMiddleware.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/TradeCryptoMiddleware.kt index 2f26b2a39a..a21351a6b9 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/TradeCryptoMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/TradeCryptoMiddleware.kt @@ -54,6 +54,15 @@ object TradeCryptoMiddleware { } private fun proceedBuyAction(state: () -> AppState?, action: TradeCryptoAction.Buy) { + val isOnrampEnabled = store.inject(DaggerGraphState::onrampFeatureToggles).isFeatureEnabled + if (isOnrampEnabled) proceedWithOnramp() else proceedWithLegacyBuyAction(state, action) + } + + private fun proceedWithOnramp() { + store.dispatchNavigationAction { push(AppRoute.Onramp) } + } + + private fun proceedWithLegacyBuyAction(state: () -> AppState?, action: TradeCryptoAction.Buy) { val networkAddress = action.cryptoCurrencyStatus.value.networkAddress ?.defaultAddress ?.let(NetworkAddress.Address::value) diff --git a/app/src/main/java/com/tangem/tap/routing/utils/ChildFactory.kt b/app/src/main/java/com/tangem/tap/routing/utils/ChildFactory.kt index 04e0e5f804..cca2474fa1 100644 --- a/app/src/main/java/com/tangem/tap/routing/utils/ChildFactory.kt +++ b/app/src/main/java/com/tangem/tap/routing/utils/ChildFactory.kt @@ -11,6 +11,7 @@ import com.tangem.features.disclaimer.api.components.DisclaimerComponent import com.tangem.features.managetokens.component.ManageTokensComponent import com.tangem.features.managetokens.component.ManageTokensSource import com.tangem.features.markets.details.MarketsTokenDetailsComponent +import com.tangem.features.onramp.component.OnrampComponent import com.tangem.features.pushnotifications.api.navigation.PushNotificationsRouter import com.tangem.features.send.api.navigation.SendRouter import com.tangem.features.staking.api.navigation.StakingRouter @@ -46,6 +47,7 @@ internal class ChildFactory @Inject constructor( private val disclaimerComponentFactory: DisclaimerComponent.Factory, private val manageTokensComponentFactory: ManageTokensComponent.Factory, private val marketsTokenDetailsComponentFactory: MarketsTokenDetailsComponent.Factory, + private val onrampComponentFactory: OnrampComponent.Factory, private val sendRouter: SendRouter, private val tokenDetailsRouter: TokenDetailsRouter, private val walletRouter: WalletRouter, @@ -186,6 +188,13 @@ internal class ChildFactory @Inject constructor( componentFactory = marketsTokenDetailsComponentFactory, ) } + is AppRoute.Onramp -> { + route.asComponentChild( + contextProvider = contextProvider(route, contextFactory), + params = OnrampComponent.Params(), + componentFactory = onrampComponentFactory, + ) + } } } diff --git a/common/routing/src/main/kotlin/com/tangem/common/routing/AppRoute.kt b/common/routing/src/main/kotlin/com/tangem/common/routing/AppRoute.kt index 398cb634a8..94e8f8487c 100644 --- a/common/routing/src/main/kotlin/com/tangem/common/routing/AppRoute.kt +++ b/common/routing/src/main/kotlin/com/tangem/common/routing/AppRoute.kt @@ -288,4 +288,7 @@ sealed class AppRoute(val path: String) : Route { val source: String, ) } + + @Serializable + data object Onramp : AppRoute(path = "/onramp") } \ No newline at end of file diff --git a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/entity/item/CurrencyNetworkUM.kt b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/entity/item/CurrencyNetworkUM.kt index bdec914faa..fbe35f92e4 100644 --- a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/entity/item/CurrencyNetworkUM.kt +++ b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/entity/item/CurrencyNetworkUM.kt @@ -14,6 +14,4 @@ internal data class CurrencyNetworkUM( ) : SelectableItemUM { override val id: String = network.id.value - - data class LongTapConfig(val contractAddress: String, val onLongTap: () -> Unit) } \ No newline at end of file diff --git a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/ui/CustomTokenSelectorContent.kt b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/ui/CustomTokenSelectorContent.kt index 21f3daf5e1..2fed1003f0 100644 --- a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/ui/CustomTokenSelectorContent.kt +++ b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/ui/CustomTokenSelectorContent.kt @@ -285,6 +285,18 @@ private class CustomTokenNetworkSelectorComponentPreviewProvider : onDerivationPathSelected = {}, ), ), + PreviewCustomTokenSelectorComponent( + params = CustomTokenSelectorComponent.Params.NetworkSelector( + userWalletId = UserWalletId(stringValue = "321"), + selectedNetwork = SelectedNetwork( + id = Network.ID(value = "0"), + name = "Ethereum", + derivationPath = Network.DerivationPath.Card("m/44'/0'/0'/0/0"), + canHandleTokens = true, + ), + onNetworkSelected = {}, + ), + ), ) } // endregion Preview \ No newline at end of file diff --git a/features/onramp/api/src/main/kotlin/com/tangem/features/onramp/component/OnrampComponent.kt b/features/onramp/api/src/main/kotlin/com/tangem/features/onramp/component/OnrampComponent.kt index b5cd6141fb..16ac6b24f7 100644 --- a/features/onramp/api/src/main/kotlin/com/tangem/features/onramp/component/OnrampComponent.kt +++ b/features/onramp/api/src/main/kotlin/com/tangem/features/onramp/component/OnrampComponent.kt @@ -5,5 +5,7 @@ import com.tangem.core.ui.decompose.ComposableContentComponent interface OnrampComponent : ComposableContentComponent { - interface Factory : ComponentFactory + class Params + + interface Factory : ComponentFactory } \ No newline at end of file diff --git a/features/onramp/impl/build.gradle.kts b/features/onramp/impl/build.gradle.kts index 37ef2d093d..94b9c2785a 100644 --- a/features/onramp/impl/build.gradle.kts +++ b/features/onramp/impl/build.gradle.kts @@ -1,6 +1,7 @@ plugins { alias(deps.plugins.android.library) alias(deps.plugins.kotlin.android) + alias(deps.plugins.kotlin.serialization) alias(deps.plugins.kotlin.kapt) alias(deps.plugins.hilt.android) id("configuration") @@ -23,11 +24,11 @@ dependencies { implementation(deps.hilt.android) kapt(deps.hilt.kapt) - /* AndroidX */ + /** AndroidX */ implementation(deps.androidx.activity.compose) implementation(deps.lifecycle.compose) - /* Compose */ + /** Compose */ implementation(deps.compose.ui) implementation(deps.compose.ui.tooling) implementation(deps.compose.accompanist.systemUiController) @@ -35,4 +36,8 @@ dependencies { implementation(deps.compose.material3) implementation(deps.compose.shimmer) implementation(deps.compose.coil) + implementation(deps.compose.material) + + /** Other */ + implementation(deps.decompose.ext.compose) } \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/component/ConfirmResidencyComponent.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/component/ConfirmResidencyComponent.kt new file mode 100644 index 0000000000..74742aa575 --- /dev/null +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/component/ConfirmResidencyComponent.kt @@ -0,0 +1,16 @@ +package com.tangem.features.onramp.component + +import com.tangem.core.decompose.factory.ComponentFactory +import com.tangem.core.ui.decompose.ComposableBottomSheetComponent + +internal interface ConfirmResidencyComponent : ComposableBottomSheetComponent { + + data class Params( + val countryName: String, + val isOnrampSupported: Boolean, + val countryFlagUrl: String, + val onDismiss: () -> Unit, + ) + + interface Factory : ComponentFactory +} \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/component/impl/DefaultConfirmResidencyComponent.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/component/impl/DefaultConfirmResidencyComponent.kt new file mode 100644 index 0000000000..177e697eb8 --- /dev/null +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/component/impl/DefaultConfirmResidencyComponent.kt @@ -0,0 +1,55 @@ +package com.tangem.features.onramp.component.impl + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.tangem.core.decompose.context.AppComponentContext +import com.tangem.core.decompose.model.getOrCreateModel +import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig +import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent +import com.tangem.features.onramp.component.ConfirmResidencyComponent +import com.tangem.features.onramp.model.ConfirmResidencyModel +import com.tangem.features.onramp.ui.ConfirmResidencyBottomSheet +import com.tangem.features.onramp.ui.ConfirmResidencyBottomSheetContent +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject + +internal class DefaultConfirmResidencyComponent @AssistedInject constructor( + @Assisted context: AppComponentContext, + @Assisted private val params: ConfirmResidencyComponent.Params, +) : ConfirmResidencyComponent, AppComponentContext by context { + + private val model: ConfirmResidencyModel = getOrCreateModel(params) + + override fun dismiss() { + params.onDismiss() + } + + @Composable + override fun BottomSheet() { + val state by model.state.collectAsStateWithLifecycle() + val bottomSheetConfig = remember { + TangemBottomSheetConfig( + isShow = true, + onDismissRequest = ::dismiss, + content = TangemBottomSheetConfigContent.Empty, + ) + } + ConfirmResidencyBottomSheet( + config = bottomSheetConfig, + content = { modifier -> + ConfirmResidencyBottomSheetContent(model = state, modifier = modifier) + }, + ) + } + + @AssistedFactory + interface Factory : ConfirmResidencyComponent.Factory { + override fun create( + context: AppComponentContext, + params: ConfirmResidencyComponent.Params, + ): DefaultConfirmResidencyComponent + } +} \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/component/impl/DefaultOnrampComponent.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/component/impl/DefaultOnrampComponent.kt new file mode 100644 index 0000000000..7ec0fb692a --- /dev/null +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/component/impl/DefaultOnrampComponent.kt @@ -0,0 +1,64 @@ +package com.tangem.features.onramp.component.impl + +import androidx.activity.compose.BackHandler +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.ui.Modifier +import com.arkivanov.decompose.ComponentContext +import com.arkivanov.decompose.extensions.compose.jetpack.subscribeAsState +import com.arkivanov.decompose.router.slot.childSlot +import com.arkivanov.decompose.router.slot.dismiss +import com.tangem.core.decompose.context.AppComponentContext +import com.tangem.core.decompose.context.childByContext +import com.tangem.core.decompose.model.getOrCreateModel +import com.tangem.core.ui.decompose.ComposableBottomSheetComponent +import com.tangem.features.onramp.component.OnrampComponent +import com.tangem.features.onramp.component.ConfirmResidencyComponent +import com.tangem.features.onramp.entity.OnrampBottomSheetConfig +import com.tangem.features.onramp.model.OnrampModel +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject + +internal class DefaultOnrampComponent @AssistedInject constructor( + @Assisted context: AppComponentContext, + @Assisted private val params: OnrampComponent.Params, + private val confirmResidencyComponentFactory: ConfirmResidencyComponent.Factory, +) : OnrampComponent, AppComponentContext by context { + + private val model: OnrampModel = getOrCreateModel(params) + private val confirmResidencyBottomSheetSlot = childSlot( + source = model.bottomSheetNavigation, + serializer = null, + handleBackButton = false, + childFactory = ::bottomSheetChild, + ) + + @Composable + override fun Content(modifier: Modifier) { + val bottomSheet by confirmResidencyBottomSheetSlot.subscribeAsState() + BackHandler(onBack = router::pop) + + bottomSheet.child?.instance?.BottomSheet() + } + + private fun bottomSheetChild( + config: OnrampBottomSheetConfig, + componentContext: ComponentContext, + ): ComposableBottomSheetComponent = when (config) { + OnrampBottomSheetConfig.ConfirmResidency -> confirmResidencyComponentFactory.create( + context = childByContext(componentContext), + params = ConfirmResidencyComponent.Params( + countryName = "United States", + isOnrampSupported = true, + countryFlagUrl = "https://hatscripts.github.io/circle-flags/flags/us.svg", + onDismiss = model.bottomSheetNavigation::dismiss, + ), + ) + } + + @AssistedFactory + interface Factory : OnrampComponent.Factory { + override fun create(context: AppComponentContext, params: OnrampComponent.Params): DefaultOnrampComponent + } +} \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/component/preview/PreviewConfirmResidencyComponent.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/component/preview/PreviewConfirmResidencyComponent.kt new file mode 100644 index 0000000000..b5e167f157 --- /dev/null +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/component/preview/PreviewConfirmResidencyComponent.kt @@ -0,0 +1,46 @@ +package com.tangem.features.onramp.component.preview + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig +import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent +import com.tangem.core.ui.extensions.stringReference +import com.tangem.features.onramp.component.ConfirmResidencyComponent +import com.tangem.features.onramp.entity.ConfirmResidencyUM +import com.tangem.features.onramp.ui.ConfirmResidencyBottomSheet +import com.tangem.features.onramp.ui.ConfirmResidencyBottomSheetContent +import kotlinx.coroutines.flow.MutableStateFlow + +internal class PreviewConfirmResidencyComponent( + initialState: ConfirmResidencyUM = ConfirmResidencyUM( + country = "United States", + countryFlagUrl = "https://hatscripts.github.io/circle-flags/flags/us.svg", + isCountrySupported = true, + primaryButtonConfig = ConfirmResidencyUM.ActionButtonConfig(onClick = {}, text = stringReference("Confirm")), + secondaryButtonConfig = ConfirmResidencyUM.ActionButtonConfig(onClick = {}, text = stringReference("Change")), + ), +) : ConfirmResidencyComponent { + + private val previewState: MutableStateFlow = MutableStateFlow(initialState) + + override fun dismiss() { + /* no-op */ + } + + @Composable + override fun BottomSheet() { + val state by previewState.collectAsStateWithLifecycle() + val bottomSheetConfig = TangemBottomSheetConfig( + isShow = true, + onDismissRequest = ::dismiss, + content = TangemBottomSheetConfigContent.Empty, + ) + ConfirmResidencyBottomSheet( + config = bottomSheetConfig, + content = { modifier -> + ConfirmResidencyBottomSheetContent(model = state, modifier = modifier) + }, + ) + } +} \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/di/OnrampComponentModule.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/di/OnrampComponentModule.kt new file mode 100644 index 0000000000..6f27d391db --- /dev/null +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/di/OnrampComponentModule.kt @@ -0,0 +1,26 @@ +package com.tangem.features.onramp.di + +import com.tangem.features.onramp.component.ConfirmResidencyComponent +import com.tangem.features.onramp.component.OnrampComponent +import com.tangem.features.onramp.component.impl.DefaultConfirmResidencyComponent +import com.tangem.features.onramp.component.impl.DefaultOnrampComponent +import dagger.Binds +import dagger.Module +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +internal interface OnrampComponentModule { + + @Binds + @Singleton + fun bindOnrampComponentFactory(factory: DefaultOnrampComponent.Factory): OnrampComponent.Factory + + @Binds + @Singleton + fun bindConfirmResidencyComponentFactory( + factory: DefaultConfirmResidencyComponent.Factory, + ): ConfirmResidencyComponent.Factory +} \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/di/FeatureModule.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/di/OnrampFeatureModule.kt similarity index 94% rename from features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/di/FeatureModule.kt rename to features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/di/OnrampFeatureModule.kt index 2c39321176..cac4df29b9 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/di/FeatureModule.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/di/OnrampFeatureModule.kt @@ -11,7 +11,7 @@ import javax.inject.Singleton @Module @InstallIn(SingletonComponent::class) -internal object FeatureModule { +internal object OnrampFeatureModule { @Provides @Singleton diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/di/OnrampModelModule.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/di/OnrampModelModule.kt new file mode 100644 index 0000000000..41f90f6d5a --- /dev/null +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/di/OnrampModelModule.kt @@ -0,0 +1,26 @@ +package com.tangem.features.onramp.di + +import com.tangem.core.decompose.di.DecomposeComponent +import com.tangem.core.decompose.model.Model +import com.tangem.features.onramp.model.OnrampModel +import com.tangem.features.onramp.model.ConfirmResidencyModel +import dagger.Binds +import dagger.Module +import dagger.hilt.InstallIn +import dagger.multibindings.ClassKey +import dagger.multibindings.IntoMap + +@Module +@InstallIn(DecomposeComponent::class) +internal interface OnrampModelModule { + + @Binds + @IntoMap + @ClassKey(OnrampModel::class) + fun provideOnrampModel(model: OnrampModel): Model + + @Binds + @IntoMap + @ClassKey(ConfirmResidencyModel::class) + fun provideConfirmResidencyModel(model: ConfirmResidencyModel): Model +} \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/entity/ConfirmResidencyUM.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/entity/ConfirmResidencyUM.kt new file mode 100644 index 0000000000..106df7e1bc --- /dev/null +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/entity/ConfirmResidencyUM.kt @@ -0,0 +1,16 @@ +package com.tangem.features.onramp.entity + +import com.tangem.core.ui.extensions.TextReference + +data class ConfirmResidencyUM( + val country: String, + val countryFlagUrl: String, + val isCountrySupported: Boolean, + val primaryButtonConfig: ActionButtonConfig, + val secondaryButtonConfig: ActionButtonConfig, +) { + data class ActionButtonConfig( + val onClick: () -> Unit, + val text: TextReference, + ) +} \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/entity/OnrampBottomSheetConfig.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/entity/OnrampBottomSheetConfig.kt new file mode 100644 index 0000000000..81289b4cdf --- /dev/null +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/entity/OnrampBottomSheetConfig.kt @@ -0,0 +1,10 @@ +package com.tangem.features.onramp.entity + +import kotlinx.serialization.Serializable + +@Serializable +internal sealed class OnrampBottomSheetConfig { + + @Serializable + data object ConfirmResidency : OnrampBottomSheetConfig() +} \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/model/ConfirmResidencyModel.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/model/ConfirmResidencyModel.kt new file mode 100644 index 0000000000..9457285bd3 --- /dev/null +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/model/ConfirmResidencyModel.kt @@ -0,0 +1,53 @@ +package com.tangem.features.onramp.model + +import com.tangem.core.decompose.di.ComponentScoped +import com.tangem.core.decompose.model.Model +import com.tangem.core.decompose.model.ParamsContainer +import com.tangem.core.ui.extensions.resourceReference +import com.tangem.features.onramp.component.ConfirmResidencyComponent +import com.tangem.features.onramp.entity.ConfirmResidencyUM +import com.tangem.features.onramp.impl.R +import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import kotlinx.coroutines.flow.MutableStateFlow +import javax.inject.Inject + +@ComponentScoped +internal class ConfirmResidencyModel @Inject constructor( + override val dispatchers: CoroutineDispatcherProvider, + paramsContainer: ParamsContainer, +) : Model() { + + private val params: ConfirmResidencyComponent.Params = paramsContainer.require() + val state: MutableStateFlow = MutableStateFlow( + ConfirmResidencyUM( + country = params.countryName, + countryFlagUrl = params.countryFlagUrl, + isCountrySupported = params.isOnrampSupported, + primaryButtonConfig = getPrimaryButtonConfig(), + secondaryButtonConfig = ConfirmResidencyUM.ActionButtonConfig( + onClick = ::onChangeClick, + text = resourceReference(R.string.common_change), + ), + ), + ) + + private fun getPrimaryButtonConfig() = if (params.isOnrampSupported) { + ConfirmResidencyUM.ActionButtonConfig( + onClick = params.onDismiss, + text = resourceReference(R.string.common_confirm), + ) + } else { + ConfirmResidencyUM.ActionButtonConfig( + onClick = ::onCloseClick, + text = resourceReference(R.string.common_close), + ) + } + + @Suppress("EmptyFunctionBlock") + private fun onCloseClick() { + } + + private fun onChangeClick() { + // TODO: [REDACTED_JIRA] + } +} \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/model/OnrampModel.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/model/OnrampModel.kt new file mode 100644 index 0000000000..d1f35e1fca --- /dev/null +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/model/OnrampModel.kt @@ -0,0 +1,38 @@ +package com.tangem.features.onramp.model + +import com.arkivanov.decompose.router.slot.SlotNavigation +import com.arkivanov.decompose.router.slot.activate +import com.tangem.core.decompose.di.ComponentScoped +import com.tangem.core.decompose.model.Model +import com.tangem.core.decompose.model.ParamsContainer +import com.tangem.core.decompose.navigation.Router +import com.tangem.features.onramp.component.OnrampComponent +import com.tangem.features.onramp.entity.OnrampBottomSheetConfig +import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch +import javax.inject.Inject + +@Suppress("MagicNumber") +@ComponentScoped +internal class OnrampModel @Inject constructor( + override val dispatchers: CoroutineDispatcherProvider, + private val router: Router, + paramsContainer: ParamsContainer, +) : Model() { + + @Suppress("UnusedPrivateMember") + private val params: OnrampComponent.Params = paramsContainer.require() + val bottomSheetNavigation: SlotNavigation = SlotNavigation() + + init { + modelScope.launch { + delay(1500) + bottomSheetNavigation.activate(OnrampBottomSheetConfig.ConfirmResidency) + } + } + + fun pop() { + router.pop() + } +} \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/ui/ResidenceBottomSheet.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/ui/ResidenceBottomSheet.kt new file mode 100644 index 0000000000..a950da23ab --- /dev/null +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/ui/ResidenceBottomSheet.kt @@ -0,0 +1,165 @@ +package com.tangem.features.onramp.ui + +import android.content.res.Configuration +import androidx.compose.foundation.layout.* +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.tooling.preview.PreviewParameter +import androidx.compose.ui.tooling.preview.PreviewParameterProvider +import coil.compose.SubcomposeAsyncImage +import coil.request.ImageRequest +import com.tangem.core.ui.components.CircleShimmer +import com.tangem.core.ui.components.PrimaryButton +import com.tangem.core.ui.components.SecondaryButton +import com.tangem.core.ui.components.appbar.TangemTopAppBar +import com.tangem.core.ui.components.appbar.TangemTopAppBarHeight +import com.tangem.core.ui.components.bottomsheets.TangemBottomSheet +import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig +import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent +import com.tangem.core.ui.extensions.resolveReference +import com.tangem.core.ui.extensions.resourceReference +import com.tangem.core.ui.extensions.stringReference +import com.tangem.core.ui.res.TangemTheme +import com.tangem.core.ui.res.TangemThemePreview +import com.tangem.features.onramp.component.ConfirmResidencyComponent +import com.tangem.features.onramp.component.preview.PreviewConfirmResidencyComponent +import com.tangem.features.onramp.entity.ConfirmResidencyUM +import com.tangem.features.onramp.impl.R + +@Composable +internal fun ConfirmResidencyBottomSheet(config: TangemBottomSheetConfig, content: @Composable (Modifier) -> Unit) { + TangemBottomSheet( + config = config, + addBottomInsets = true, + containerColor = TangemTheme.colors.background.tertiary, + title = { _ -> Title() }, + content = { + val contentModifier = Modifier + .padding(horizontal = TangemTheme.dimens.spacing16) + .padding(bottom = TangemTheme.dimens.spacing16) + .fillMaxWidth() + .wrapContentHeight() + + content(contentModifier) + }, + ) +} + +@Composable +private fun Title(modifier: Modifier = Modifier) { + TangemTopAppBar( + modifier = modifier, + title = resourceReference(R.string.onramp_residency_bottomsheet_title), + titleAlignment = Alignment.CenterHorizontally, + height = TangemTopAppBarHeight.BOTTOM_SHEET, + ) +} + +@Composable +internal fun ConfirmResidencyBottomSheetContent(model: ConfirmResidencyUM, modifier: Modifier = Modifier) { + Column(modifier = modifier) { + CountryContent( + modifier = Modifier + .fillMaxWidth() + .padding(vertical = TangemTheme.dimens.spacing24), + name = model.country, + flagUrl = model.countryFlagUrl, + isCountrySupported = model.isCountrySupported, + ) + SecondaryButton( + modifier = Modifier.fillMaxWidth(), + text = model.secondaryButtonConfig.text.resolveReference(), + onClick = model.secondaryButtonConfig.onClick, + ) + PrimaryButton( + modifier = Modifier + .fillMaxWidth() + .padding(top = TangemTheme.dimens.spacing12), + text = model.primaryButtonConfig.text.resolveReference(), + onClick = model.primaryButtonConfig.onClick, + ) + } +} + +@Composable +private fun CountryContent(name: String, flagUrl: String, isCountrySupported: Boolean, modifier: Modifier = Modifier) { + Column( + modifier = modifier, + horizontalAlignment = Alignment.CenterHorizontally, + ) { + val flagSize = with(LocalDensity.current) { TangemTheme.dimens.size36.roundToPx() } + SubcomposeAsyncImage( + modifier = Modifier.size(TangemTheme.dimens.size36), + model = ImageRequest.Builder(LocalContext.current) + .size(size = flagSize) + .data(flagUrl) + .memoryCacheKey(flagUrl + flagSize) + .crossfade(true) + .allowHardware(false) + .build(), + loading = { CircleShimmer() }, + contentDescription = null, + ) + + Text( + modifier = Modifier.padding(top = TangemTheme.dimens.spacing12), + text = name, + color = TangemTheme.colors.text.primary1, + style = TangemTheme.typography.subtitle1, + maxLines = 1, + ) + + Text( + modifier = Modifier.padding(top = TangemTheme.dimens.spacing6), + text = stringResource( + id = if (isCountrySupported) { + R.string.onramp_residency_bottomsheet_country_subtitle + } else { + R.string.onramp_residency_bottomsheet_country_not_supported + }, + ), + color = if (isCountrySupported) TangemTheme.colors.text.tertiary else TangemTheme.colors.text.warning, + style = TangemTheme.typography.body2, + ) + } +} + +// region Preview +@Composable +@Preview(showBackground = true) +@Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES) +private fun Preview_ConfirmResidencyBottomSheet( + @PreviewParameter(ConfirmResidencyComponentPreviewProvider::class) component: ConfirmResidencyComponent, +) { + TangemThemePreview { + component.BottomSheet() + } +} + +private class ConfirmResidencyComponentPreviewProvider : PreviewParameterProvider { + override val values: Sequence + get() = sequenceOf( + PreviewConfirmResidencyComponent(), + PreviewConfirmResidencyComponent( + initialState = ConfirmResidencyUM( + country = "Russia", + countryFlagUrl = "https://hatscripts.github.io/circle-flags/flags/ru.svg", + isCountrySupported = false, + primaryButtonConfig = ConfirmResidencyUM.ActionButtonConfig( + onClick = {}, + text = stringReference("Close"), + ), + secondaryButtonConfig = ConfirmResidencyUM.ActionButtonConfig( + onClick = {}, + text = stringReference("Change"), + ), + ), + ), + ) +} \ No newline at end of file From 7834cc5a8736e5f65fc291efdfacb3bbcbba37a7 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 25 Oct 2024 12:16:14 +0400 Subject: [PATCH 024/106] Updated on 2026-08-14 --- .../com/tangem/tap/ApplicationEntryPoint.kt | 4 +-- .../java/com/tangem/tap/TangemApplication.kt | 8 +++--- .../tap/domain/twins/TwinCardsManager.kt | 26 ++++++------------ .../tap/proxy/redux/DaggerGraphState.kt | 4 +-- .../di/local/config/ConfigModule.kt | 13 ++++++++- .../issuers/DefaultIssuersConfigStorage.kt | 27 +++++++++++++++++++ .../config/issuers/IssuersConfigStorage.kt | 14 ++++++++++ .../local/config/issuers/models/Issuer.kt | 10 +++++++ 8 files changed, 79 insertions(+), 27 deletions(-) create mode 100644 core/datasource/src/main/java/com/tangem/datasource/local/config/issuers/DefaultIssuersConfigStorage.kt create mode 100644 core/datasource/src/main/java/com/tangem/datasource/local/config/issuers/IssuersConfigStorage.kt create mode 100644 core/datasource/src/main/java/com/tangem/datasource/local/config/issuers/models/Issuer.kt diff --git a/app/src/main/java/com/tangem/tap/ApplicationEntryPoint.kt b/app/src/main/java/com/tangem/tap/ApplicationEntryPoint.kt index a425711dd5..e9db6824f0 100644 --- a/app/src/main/java/com/tangem/tap/ApplicationEntryPoint.kt +++ b/app/src/main/java/com/tangem/tap/ApplicationEntryPoint.kt @@ -8,9 +8,9 @@ import com.tangem.core.analytics.filter.OneTimeEventFilter import com.tangem.core.featuretoggle.manager.FeatureTogglesManager import com.tangem.core.navigation.share.ShareManager import com.tangem.core.navigation.url.UrlOpener -import com.tangem.datasource.asset.loader.AssetLoader import com.tangem.datasource.connection.NetworkConnectionManager import com.tangem.datasource.local.config.environment.EnvironmentConfigStorage +import com.tangem.datasource.local.config.issuers.IssuersConfigStorage import com.tangem.datasource.local.preferences.AppPreferencesStore import com.tangem.domain.appcurrency.repository.AppCurrencyRepository import com.tangem.domain.apptheme.GetAppThemeModeUseCase @@ -51,7 +51,7 @@ interface ApplicationEntryPoint { fun getAppStateHolder(): AppStateHolder - fun getAssetLoader(): AssetLoader + fun getIssuersConfigStorage(): IssuersConfigStorage fun getFeatureTogglesManager(): FeatureTogglesManager diff --git a/app/src/main/java/com/tangem/tap/TangemApplication.kt b/app/src/main/java/com/tangem/tap/TangemApplication.kt index 530337b2cd..36f91a010b 100644 --- a/app/src/main/java/com/tangem/tap/TangemApplication.kt +++ b/app/src/main/java/com/tangem/tap/TangemApplication.kt @@ -20,10 +20,10 @@ import com.tangem.core.analytics.models.Basic.TransactionSent.WalletForm import com.tangem.core.featuretoggle.manager.FeatureTogglesManager import com.tangem.datasource.api.common.MoshiConverter import com.tangem.datasource.api.common.createNetworkLoggingInterceptor -import com.tangem.datasource.asset.loader.AssetLoader import com.tangem.datasource.connection.NetworkConnectionManager import com.tangem.datasource.local.config.environment.EnvironmentConfig import com.tangem.datasource.local.config.environment.EnvironmentConfigStorage +import com.tangem.datasource.local.config.issuers.IssuersConfigStorage import com.tangem.datasource.local.preferences.AppPreferencesStore import com.tangem.domain.appcurrency.repository.AppCurrencyRepository import com.tangem.domain.apptheme.GetAppThemeModeUseCase @@ -86,8 +86,8 @@ abstract class TangemApplication : Application(), ImageLoaderFactory { private val environmentConfigStorage: EnvironmentConfigStorage get() = entryPoint.getEnvironmentConfigStorage() - private val assetLoader: AssetLoader - get() = entryPoint.getAssetLoader() + private val issuersConfigStorage: IssuersConfigStorage + get() = entryPoint.getIssuersConfigStorage() private val featureTogglesManager: FeatureTogglesManager get() = entryPoint.getFeatureTogglesManager() @@ -269,7 +269,7 @@ abstract class TangemApplication : Application(), ImageLoaderFactory { saveBlockchainErrorUseCase = saveBlockchainErrorUseCase, sendFeedbackEmailUseCase = sendFeedbackEmailUseCase, getCardInfoUseCase = getCardInfoUseCase, - assetLoader = assetLoader, + issuersConfigStorage = issuersConfigStorage, urlOpener = urlOpener, shareManager = shareManager, appRouter = appRouter, diff --git a/app/src/main/java/com/tangem/tap/domain/twins/TwinCardsManager.kt b/app/src/main/java/com/tangem/tap/domain/twins/TwinCardsManager.kt index 7f242bb845..beaf919a98 100644 --- a/app/src/main/java/com/tangem/tap/domain/twins/TwinCardsManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/twins/TwinCardsManager.kt @@ -13,17 +13,19 @@ import com.tangem.tap.common.extensions.inject import com.tangem.tap.proxy.redux.DaggerGraphState import com.tangem.tap.store import com.tangem.tap.tangemSdkManager -import kotlinx.coroutines.flow.MutableStateFlow class TwinCardsManager(card: CardDTO) { + + private val issuersConfigStorage by lazy(mode = LazyThreadSafetyMode.NONE) { + store.inject(DaggerGraphState::issuersConfigStorage) + } + private val firstCardId: String = card.cardId private val publicKey: String = card.issuer.publicKey.toHexString() private var currentCardPublicKey: String? = null private var secondCardPublicKey: String? = null - private val issuerKeyPairFlow = MutableStateFlow(value = null) - suspend fun createFirstWallet(message: Message): CompletionResult { val response = tangemSdkManager.createFirstTwinWallet(cardId = firstCardId, initialMessage = message) @@ -70,23 +72,11 @@ class TwinCardsManager(card: CardDTO) { } private suspend fun getIssuerKeys(): KeyPair { - issuerKeyPairFlow.value?.let { return it } - - val assetLoader = store.inject(DaggerGraphState::assetLoader) - val issuer = assetLoader.loadList(fileName = ISSUERS_FILE_NAME) - .first { it.publicKey == publicKey } + val issuer = issuersConfigStorage.getConfig().first { it.publicKey == publicKey } return KeyPair( publicKey = issuer.publicKey.hexToBytes(), privateKey = issuer.privateKey.hexToBytes(), - ).also { - issuerKeyPairFlow.value = it - } + ) } - - private companion object { - const val ISSUERS_FILE_NAME = "tangem-app-config/issuers" - } -} - -private class Issuer(val privateKey: String, val publicKey: String) \ No newline at end of file +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphState.kt b/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphState.kt index 8d6b257f74..0a0c716fc6 100644 --- a/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphState.kt +++ b/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphState.kt @@ -7,8 +7,8 @@ import com.tangem.common.routing.AppRouter import com.tangem.core.navigation.email.EmailSender import com.tangem.core.navigation.share.ShareManager import com.tangem.core.navigation.url.UrlOpener -import com.tangem.datasource.asset.loader.AssetLoader import com.tangem.datasource.connection.NetworkConnectionManager +import com.tangem.datasource.local.config.issuers.IssuersConfigStorage import com.tangem.domain.appcurrency.repository.AppCurrencyRepository import com.tangem.domain.apptheme.repository.AppThemeModeRepository import com.tangem.domain.balancehiding.repositories.BalanceHidingRepository @@ -79,7 +79,7 @@ data class DaggerGraphState( val saveBlockchainErrorUseCase: SaveBlockchainErrorUseCase? = null, val sendFeedbackEmailUseCase: SendFeedbackEmailUseCase? = null, val getCardInfoUseCase: GetCardInfoUseCase? = null, - val assetLoader: AssetLoader? = null, + val issuersConfigStorage: IssuersConfigStorage? = null, val stakingRouter: StakingRouter? = null, val urlOpener: UrlOpener? = null, val shareManager: ShareManager? = null, diff --git a/core/datasource/src/main/java/com/tangem/datasource/di/local/config/ConfigModule.kt b/core/datasource/src/main/java/com/tangem/datasource/di/local/config/ConfigModule.kt index 109e138c0d..0ed4457201 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/di/local/config/ConfigModule.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/di/local/config/ConfigModule.kt @@ -4,6 +4,8 @@ import com.tangem.datasource.asset.loader.AssetLoader import com.tangem.datasource.local.config.environment.DefaultEnvironmentConfigStorage import com.tangem.datasource.local.config.environment.EnvironmentConfig import com.tangem.datasource.local.config.environment.EnvironmentConfigStorage +import com.tangem.datasource.local.config.issuers.DefaultIssuersConfigStorage +import com.tangem.datasource.local.config.issuers.IssuersConfigStorage import com.tangem.datasource.local.config.providers.BlockchainProvidersStorage import com.tangem.datasource.local.config.providers.DefaultBlockchainProvidersStorage import com.tangem.datasource.local.config.testnet.DefaultTestnetTokensStorage @@ -30,7 +32,7 @@ internal object ConfigModule { @Provides @Singleton - fun providesTestnetTokensStorage(assetLoader: AssetLoader): TestnetTokensStorage { + fun provideTestnetTokensStorage(assetLoader: AssetLoader): TestnetTokensStorage { return DefaultTestnetTokensStorage(assetLoader) } @@ -42,4 +44,13 @@ internal object ConfigModule { runtimeStateStore = RuntimeStateStore(defaultValue = emptyMap()), ) } + + @Provides + @Singleton + fun provideIssuersConfigStorage(assetLoader: AssetLoader): IssuersConfigStorage { + return DefaultIssuersConfigStorage( + assetLoader = assetLoader, + runtimeStateStore = RuntimeStateStore(defaultValue = emptyList()), + ) + } } \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/config/issuers/DefaultIssuersConfigStorage.kt b/core/datasource/src/main/java/com/tangem/datasource/local/config/issuers/DefaultIssuersConfigStorage.kt new file mode 100644 index 0000000000..0d9de3ea5c --- /dev/null +++ b/core/datasource/src/main/java/com/tangem/datasource/local/config/issuers/DefaultIssuersConfigStorage.kt @@ -0,0 +1,27 @@ +package com.tangem.datasource.local.config.issuers + +import com.tangem.datasource.asset.loader.AssetLoader +import com.tangem.datasource.local.config.issuers.models.Issuer +import com.tangem.datasource.local.datastore.RuntimeStateStore + +internal class DefaultIssuersConfigStorage( + private val assetLoader: AssetLoader, + private val runtimeStateStore: RuntimeStateStore>, +) : IssuersConfigStorage { + + override suspend fun getConfig(): List { + val cachedData = runtimeStateStore.get().value + + if (cachedData.isNotEmpty()) return cachedData + + val issuers = assetLoader.loadList(fileName = ISSUERS_FILE_NAME) + + runtimeStateStore.store(value = issuers) + + return issuers + } + + private companion object { + const val ISSUERS_FILE_NAME = "tangem-app-config/issuers" + } +} \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/config/issuers/IssuersConfigStorage.kt b/core/datasource/src/main/java/com/tangem/datasource/local/config/issuers/IssuersConfigStorage.kt new file mode 100644 index 0000000000..ea2ac0708c --- /dev/null +++ b/core/datasource/src/main/java/com/tangem/datasource/local/config/issuers/IssuersConfigStorage.kt @@ -0,0 +1,14 @@ +package com.tangem.datasource.local.config.issuers + +import com.tangem.datasource.local.config.issuers.models.Issuer + +/** + * Storage for list of Twins [Issuer] + * +[REDACTED_AUTHOR] + */ +interface IssuersConfigStorage { + + /** Get config */ + suspend fun getConfig(): List +} \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/config/issuers/models/Issuer.kt b/core/datasource/src/main/java/com/tangem/datasource/local/config/issuers/models/Issuer.kt new file mode 100644 index 0000000000..a26754c4ec --- /dev/null +++ b/core/datasource/src/main/java/com/tangem/datasource/local/config/issuers/models/Issuer.kt @@ -0,0 +1,10 @@ +package com.tangem.datasource.local.config.issuers.models + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +@JsonClass(generateAdapter = true) +data class Issuer( + @Json(name = "privateKey") val privateKey: String, + @Json(name = "publicKey") val publicKey: String, +) \ No newline at end of file From 51c6619a551a2a40c6711cf9d6676419e7676ef1 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 25 Oct 2024 14:03:55 +0400 Subject: [PATCH 025/106] Updated on 2026-08-14 --- .../com/tangem/tap/ApplicationEntryPoint.kt | 12 -------- .../main/java/com/tangem/tap/MainActivity.kt | 12 -------- .../java/com/tangem/tap/TangemApplication.kt | 28 ++----------------- .../tap/common/redux/global/GlobalAction.kt | 3 -- .../common/redux/global/GlobalMiddleware.kt | 3 +- .../tap/common/redux/global/GlobalReducer.kt | 3 -- .../tap/common/redux/global/GlobalState.kt | 2 -- .../tap/proxy/redux/DaggerGraphAction.kt | 16 ----------- .../tap/proxy/redux/DaggerGraphReducer.kt | 8 ------ .../tap/proxy/redux/DaggerGraphState.kt | 24 ++-------------- 10 files changed, 5 insertions(+), 106 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/ApplicationEntryPoint.kt b/app/src/main/java/com/tangem/tap/ApplicationEntryPoint.kt index e9db6824f0..923313f69f 100644 --- a/app/src/main/java/com/tangem/tap/ApplicationEntryPoint.kt +++ b/app/src/main/java/com/tangem/tap/ApplicationEntryPoint.kt @@ -19,14 +19,11 @@ import com.tangem.domain.balancehiding.repositories.BalanceHidingRepository import com.tangem.domain.card.ScanCardProcessor import com.tangem.domain.card.repository.CardRepository import com.tangem.domain.feedback.GetCardInfoUseCase -import com.tangem.domain.feedback.SaveBlockchainErrorUseCase import com.tangem.domain.feedback.SendFeedbackEmailUseCase import com.tangem.domain.onboarding.SaveTwinsOnboardingShownUseCase import com.tangem.domain.onboarding.WasTwinsOnboardingShownUseCase import com.tangem.domain.settings.repositories.SettingsRepository import com.tangem.domain.settings.usercountry.GetUserCountryUseCase -import com.tangem.domain.tokens.repository.CurrenciesRepository -import com.tangem.domain.tokens.repository.NetworksRepository import com.tangem.domain.walletmanager.WalletManagersFacade import com.tangem.domain.wallets.legacy.UserWalletsListManager import com.tangem.domain.wallets.repository.WalletsRepository @@ -34,7 +31,6 @@ import com.tangem.domain.wallets.usecase.GenerateWalletNameUseCase import com.tangem.features.onramp.OnrampFeatureToggles import com.tangem.tap.common.log.TangemAppLoggerInitializer import com.tangem.tap.domain.scanCard.CardScanningFeatureToggles -import com.tangem.tap.domain.walletconnect2.domain.WalletConnectSessionsRepository import com.tangem.tap.features.home.featuretoggles.HomeFeatureToggles import com.tangem.tap.proxy.AppStateHolder import dagger.hilt.EntryPoint @@ -61,18 +57,12 @@ interface ApplicationEntryPoint { fun getWalletConnect2Repository(): WalletConnect2Repository - fun getWalletConnectSessionsRepository(): WalletConnectSessionsRepository - fun getScanCardProcessor(): ScanCardProcessor fun getAppCurrencyRepository(): AppCurrencyRepository fun getWalletManagersFacade(): WalletManagersFacade - fun getNetworksRepository(): NetworksRepository - - fun getCurrenciesRepository(): CurrenciesRepository - fun getAppThemeModeRepository(): AppThemeModeRepository fun getBalanceHidingRepository(): BalanceHidingRepository @@ -103,8 +93,6 @@ interface ApplicationEntryPoint { fun getSendFeedbackEmailUseCase(): SendFeedbackEmailUseCase - fun getSaveBlockchainErrorUseCase(): SaveBlockchainErrorUseCase - fun getGetCardInfoUseCase(): GetCardInfoUseCase fun getUrlOpener(): UrlOpener diff --git a/app/src/main/java/com/tangem/tap/MainActivity.kt b/app/src/main/java/com/tangem/tap/MainActivity.kt index 169f30bc6c..3109a05bcd 100644 --- a/app/src/main/java/com/tangem/tap/MainActivity.kt +++ b/app/src/main/java/com/tangem/tap/MainActivity.kt @@ -56,7 +56,6 @@ import com.tangem.features.pushnotifications.api.navigation.PushNotificationsRou import com.tangem.features.pushnotifications.api.utils.PUSH_PERMISSION import com.tangem.features.send.api.navigation.SendRouter import com.tangem.features.staking.api.navigation.StakingRouter -import com.tangem.features.tester.api.TesterRouter import com.tangem.features.tokendetails.navigation.TokenDetailsRouter import com.tangem.features.wallet.navigation.WalletRouter import com.tangem.operations.backup.BackupService @@ -113,9 +112,6 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac lateinit var appStateHolder: AppStateHolder /** Router for opening tester menu */ - @Inject - lateinit var testerRouter: TesterRouter - @Inject lateinit var cardSdkOwner: CardSdkOwner @@ -309,17 +305,9 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac store.dispatch( DaggerGraphAction.SetActivityDependencies( - testerRouter = testerRouter, scanCardUseCase = scanCardUseCase, - walletRouter = walletRouter, walletConnectInteractor = walletConnectInteractor, - tokenDetailsRouter = tokenDetailsRouter, cardSdkConfigRepository = cardSdkConfigRepository, - sendRouter = sendRouter, - qrScanningRouter = qrScanningRouter, - emailSender = emailSender, - stakingRouter = stakingRouter, - pushNotificationsRouter = pushNotificationsRouter, ), ) } diff --git a/app/src/main/java/com/tangem/tap/TangemApplication.kt b/app/src/main/java/com/tangem/tap/TangemApplication.kt index 36f91a010b..c30a00c30d 100644 --- a/app/src/main/java/com/tangem/tap/TangemApplication.kt +++ b/app/src/main/java/com/tangem/tap/TangemApplication.kt @@ -33,14 +33,11 @@ import com.tangem.domain.card.ScanCardProcessor import com.tangem.domain.card.repository.CardRepository import com.tangem.domain.common.LogConfig import com.tangem.domain.feedback.GetCardInfoUseCase -import com.tangem.domain.feedback.SaveBlockchainErrorUseCase import com.tangem.domain.feedback.SendFeedbackEmailUseCase import com.tangem.domain.onboarding.SaveTwinsOnboardingShownUseCase import com.tangem.domain.onboarding.WasTwinsOnboardingShownUseCase import com.tangem.domain.settings.repositories.SettingsRepository import com.tangem.domain.settings.usercountry.GetUserCountryUseCase -import com.tangem.domain.tokens.repository.CurrenciesRepository -import com.tangem.domain.tokens.repository.NetworksRepository import com.tangem.domain.walletmanager.WalletManagersFacade import com.tangem.domain.wallets.legacy.UserWalletsListManager import com.tangem.domain.wallets.repository.WalletsRepository @@ -54,10 +51,8 @@ import com.tangem.tap.common.images.createCoilImageLoader import com.tangem.tap.common.log.TangemAppLoggerInitializer import com.tangem.tap.common.redux.AppState import com.tangem.tap.common.redux.appReducer -import com.tangem.tap.common.redux.global.GlobalAction import com.tangem.tap.domain.scanCard.CardScanningFeatureToggles import com.tangem.tap.domain.tasks.product.DerivationsFinder -import com.tangem.tap.domain.walletconnect2.domain.WalletConnectSessionsRepository import com.tangem.tap.features.home.featuretoggles.HomeFeatureToggles import com.tangem.tap.proxy.AppStateHolder import com.tangem.tap.proxy.redux.DaggerGraphState @@ -101,9 +96,6 @@ abstract class TangemApplication : Application(), ImageLoaderFactory { private val walletConnect2Repository: WalletConnect2Repository get() = entryPoint.getWalletConnect2Repository() - private val walletConnectSessionsRepository: WalletConnectSessionsRepository - get() = entryPoint.getWalletConnectSessionsRepository() - private val scanCardProcessor: ScanCardProcessor get() = entryPoint.getScanCardProcessor() @@ -113,12 +105,6 @@ abstract class TangemApplication : Application(), ImageLoaderFactory { private val walletManagersFacade: WalletManagersFacade get() = entryPoint.getWalletManagersFacade() - private val networksRepository: NetworksRepository - get() = entryPoint.getNetworksRepository() - - private val currenciesRepository: CurrenciesRepository - get() = entryPoint.getCurrenciesRepository() - private val appThemeModeRepository: AppThemeModeRepository get() = entryPoint.getAppThemeModeRepository() @@ -164,9 +150,6 @@ abstract class TangemApplication : Application(), ImageLoaderFactory { private val sendFeedbackEmailUseCase: SendFeedbackEmailUseCase get() = entryPoint.getSendFeedbackEmailUseCase() - private val saveBlockchainErrorUseCase: SaveBlockchainErrorUseCase - get() = entryPoint.getSaveBlockchainErrorUseCase() - private val getCardInfoUseCase: GetCardInfoUseCase get() = entryPoint.getGetCardInfoUseCase() @@ -215,10 +198,7 @@ abstract class TangemApplication : Application(), ImageLoaderFactory { runBlocking { featureTogglesManager.init() - val config = environmentConfigStorage.initialize() - store.dispatch(GlobalAction.SetConfigManager(environmentConfigStorage)) - - initWithConfigDependency(environmentConfig = config) + initWithConfigDependency(environmentConfig = environmentConfigStorage.initialize()) } loadNativeLibraries() @@ -248,13 +228,10 @@ abstract class TangemApplication : Application(), ImageLoaderFactory { networkConnectionManager = networkConnectionManager, cardScanningFeatureToggles = cardScanningFeatureToggles, walletConnectRepository = walletConnect2Repository, - walletConnectSessionsRepository = walletConnectSessionsRepository, scanCardProcessor = scanCardProcessor, appCurrencyRepository = appCurrencyRepository, walletManagersFacade = walletManagersFacade, appStateHolder = appStateHolder, - networksRepository = networksRepository, - currenciesRepository = currenciesRepository, appThemeModeRepository = appThemeModeRepository, balanceHidingRepository = balanceHidingRepository, walletsRepository = walletsRepository, @@ -263,10 +240,8 @@ abstract class TangemApplication : Application(), ImageLoaderFactory { saveTwinsOnboardingShownUseCase = saveTwinsOnboardingShownUseCase, generateWalletNameUseCase = generateWalletNameUseCase, cardRepository = cardRepository, - tangemSdkLogger = tangemSdkLogger, settingsRepository = settingsRepository, blockchainSDKFactory = blockchainSDKFactory, - saveBlockchainErrorUseCase = saveBlockchainErrorUseCase, sendFeedbackEmailUseCase = sendFeedbackEmailUseCase, getCardInfoUseCase = getCardInfoUseCase, issuersConfigStorage = issuersConfigStorage, @@ -277,6 +252,7 @@ abstract class TangemApplication : Application(), ImageLoaderFactory { homeFeatureToggles = homeFeatureToggles, getUserCountryUseCase = getUserCountryUseCase, onrampFeatureToggles = onrampFeatureToggles, + environmentConfigStorage = environmentConfigStorage, ), ), ) diff --git a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalAction.kt b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalAction.kt index a9525dc46c..740f5bd460 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalAction.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalAction.kt @@ -2,7 +2,6 @@ package com.tangem.tap.common.redux.global import com.tangem.common.CompletionResult import com.tangem.core.analytics.models.AnalyticsParam -import com.tangem.datasource.local.config.environment.EnvironmentConfigStorage import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.models.scan.ScanResponse import com.tangem.domain.redux.StateDialog @@ -74,8 +73,6 @@ sealed class GlobalAction : Action { data class IsSignWithRing(val isSignWithRing: Boolean) : GlobalAction() - data class SetConfigManager(val environmentConfigStorage: EnvironmentConfigStorage) : GlobalAction() - object ExchangeManager : GlobalAction() { object Init : GlobalAction() { data class Success( diff --git a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalMiddleware.kt b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalMiddleware.kt index a0c16d69f6..636b4f40b1 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalMiddleware.kt @@ -56,8 +56,7 @@ private fun handleAction(action: Action, appState: () -> AppState?) { restoreAppCurrency() } is GlobalAction.ExchangeManager.Init -> { - val appStateSafe = appState() ?: return - val config = appStateSafe.globalState.environmentConfigStorage?.getConfigSync() ?: return + val config = store.inject(DaggerGraphState::environmentConfigStorage).getConfigSync() scope.launch { val scanResponseProvider: () -> ScanResponse? = { diff --git a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalReducer.kt b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalReducer.kt index 6884603331..d55f99d21b 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalReducer.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalReducer.kt @@ -47,9 +47,6 @@ fun globalReducer(action: Action, state: AppState, appStateHolder: AppStateHolde is GlobalAction.RestoreAppCurrency.Success -> { globalState.copy(appCurrency = action.appCurrency) } - is GlobalAction.SetConfigManager -> { - globalState.copy(environmentConfigStorage = action.environmentConfigStorage) - } is GlobalAction.UpdateWalletSignedHashes -> { val card = globalState.scanResponse?.card ?: return globalState val wallet = card.wallets diff --git a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalState.kt b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalState.kt index 0317eaaca6..b75d7370d8 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalState.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalState.kt @@ -1,6 +1,5 @@ package com.tangem.tap.common.redux.global -import com.tangem.datasource.local.config.environment.EnvironmentConfigStorage import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.apptheme.model.AppThemeMode import com.tangem.domain.models.scan.ScanResponse @@ -16,7 +15,6 @@ data class GlobalState( val onboardingState: OnboardingState = OnboardingState(), val cardVerifiedOnline: Boolean = false, val tapWalletManager: TapWalletManager = TapWalletManager(), - val environmentConfigStorage: EnvironmentConfigStorage? = null, val appCurrency: AppCurrency = AppCurrency.Default, val scanCardFailsCounter: Int = 0, val dialog: StateDialog? = null, diff --git a/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphAction.kt b/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphAction.kt index 0f3c13fbf9..86116d6849 100644 --- a/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphAction.kt +++ b/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphAction.kt @@ -1,31 +1,15 @@ package com.tangem.tap.proxy.redux -import com.tangem.core.navigation.email.EmailSender import com.tangem.domain.card.ScanCardUseCase import com.tangem.domain.card.repository.CardSdkConfigRepository -import com.tangem.feature.qrscanning.QrScanningRouter -import com.tangem.features.pushnotifications.api.navigation.PushNotificationsRouter -import com.tangem.features.send.api.navigation.SendRouter -import com.tangem.features.staking.api.navigation.StakingRouter -import com.tangem.features.tester.api.TesterRouter -import com.tangem.features.tokendetails.navigation.TokenDetailsRouter -import com.tangem.features.wallet.navigation.WalletRouter import com.tangem.tap.domain.walletconnect2.domain.WalletConnectInteractor import org.rekotlin.Action sealed interface DaggerGraphAction : Action { data class SetActivityDependencies( - val testerRouter: TesterRouter, val scanCardUseCase: ScanCardUseCase, - val walletRouter: WalletRouter, val walletConnectInteractor: WalletConnectInteractor, - val tokenDetailsRouter: TokenDetailsRouter, val cardSdkConfigRepository: CardSdkConfigRepository, - val sendRouter: SendRouter, - val qrScanningRouter: QrScanningRouter, - val emailSender: EmailSender, - val stakingRouter: StakingRouter, - val pushNotificationsRouter: PushNotificationsRouter, ) : DaggerGraphAction } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphReducer.kt b/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphReducer.kt index 2a20bab5b6..76802dd20d 100644 --- a/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphReducer.kt +++ b/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphReducer.kt @@ -13,17 +13,9 @@ object DaggerGraphReducer { private fun internalReduce(action: DaggerGraphAction, state: AppState): DaggerGraphState { return when (action) { is DaggerGraphAction.SetActivityDependencies -> state.daggerGraphState.copy( - testerRouter = action.testerRouter, scanCardUseCase = action.scanCardUseCase, - walletRouter = action.walletRouter, walletConnectInteractor = action.walletConnectInteractor, - tokenDetailsRouter = action.tokenDetailsRouter, cardSdkConfigRepository = action.cardSdkConfigRepository, - sendRouter = action.sendRouter, - qrScanningRouter = action.qrScanningRouter, - emailSender = action.emailSender, - stakingRouter = action.stakingRouter, - pushNotificationsRouter = action.pushNotificationsRouter, ) } } diff --git a/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphState.kt b/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphState.kt index 0a0c716fc6..fe6a63998e 100644 --- a/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphState.kt +++ b/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphState.kt @@ -1,13 +1,12 @@ package com.tangem.tap.proxy.redux -import com.tangem.TangemSdkLogger import com.tangem.blockchainsdk.BlockchainSDKFactory import com.tangem.blockchainsdk.signer.TransactionSignerFactory import com.tangem.common.routing.AppRouter -import com.tangem.core.navigation.email.EmailSender import com.tangem.core.navigation.share.ShareManager import com.tangem.core.navigation.url.UrlOpener import com.tangem.datasource.connection.NetworkConnectionManager +import com.tangem.datasource.local.config.environment.EnvironmentConfigStorage import com.tangem.datasource.local.config.issuers.IssuersConfigStorage import com.tangem.domain.appcurrency.repository.AppCurrencyRepository import com.tangem.domain.apptheme.repository.AppThemeModeRepository @@ -17,44 +16,31 @@ import com.tangem.domain.card.ScanCardUseCase import com.tangem.domain.card.repository.CardRepository import com.tangem.domain.card.repository.CardSdkConfigRepository import com.tangem.domain.feedback.GetCardInfoUseCase -import com.tangem.domain.feedback.SaveBlockchainErrorUseCase import com.tangem.domain.feedback.SendFeedbackEmailUseCase import com.tangem.domain.onboarding.SaveTwinsOnboardingShownUseCase import com.tangem.domain.onboarding.WasTwinsOnboardingShownUseCase import com.tangem.domain.settings.repositories.SettingsRepository import com.tangem.domain.settings.usercountry.GetUserCountryUseCase -import com.tangem.domain.tokens.repository.CurrenciesRepository -import com.tangem.domain.tokens.repository.NetworksRepository import com.tangem.domain.walletmanager.WalletManagersFacade import com.tangem.domain.wallets.legacy.UserWalletsListManager import com.tangem.domain.wallets.repository.WalletsRepository import com.tangem.domain.wallets.usecase.GenerateWalletNameUseCase -import com.tangem.feature.qrscanning.QrScanningRouter import com.tangem.features.onramp.OnrampFeatureToggles import com.tangem.features.pushnotifications.api.navigation.PushNotificationsRouter -import com.tangem.features.send.api.navigation.SendRouter import com.tangem.features.staking.api.navigation.StakingRouter -import com.tangem.features.tester.api.TesterRouter -import com.tangem.features.tokendetails.navigation.TokenDetailsRouter -import com.tangem.features.wallet.navigation.WalletRouter import com.tangem.tap.domain.scanCard.CardScanningFeatureToggles import com.tangem.tap.domain.walletconnect2.domain.LegacyWalletConnectRepository import com.tangem.tap.domain.walletconnect2.domain.WalletConnectInteractor -import com.tangem.tap.domain.walletconnect2.domain.WalletConnectSessionsRepository import com.tangem.tap.features.home.featuretoggles.HomeFeatureToggles import com.tangem.tap.proxy.AppStateHolder import org.rekotlin.StateType data class DaggerGraphState( - val testerRouter: TesterRouter? = null, val networkConnectionManager: NetworkConnectionManager? = null, val cardScanningFeatureToggles: CardScanningFeatureToggles? = null, val scanCardUseCase: ScanCardUseCase? = null, - val walletRouter: WalletRouter? = null, val walletConnectRepository: LegacyWalletConnectRepository? = null, - val walletConnectSessionsRepository: WalletConnectSessionsRepository? = null, val walletConnectInteractor: WalletConnectInteractor? = null, - val tokenDetailsRouter: TokenDetailsRouter? = null, val scanCardProcessor: ScanCardProcessor? = null, val cardSdkConfigRepository: CardSdkConfigRepository? = null, val appCurrencyRepository: AppCurrencyRepository? = null, @@ -63,20 +49,13 @@ data class DaggerGraphState( val appThemeModeRepository: AppThemeModeRepository? = null, val balanceHidingRepository: BalanceHidingRepository? = null, val walletsRepository: WalletsRepository? = null, - val networksRepository: NetworksRepository? = null, - val sendRouter: SendRouter? = null, - val qrScanningRouter: QrScanningRouter? = null, - val currenciesRepository: CurrenciesRepository? = null, val generalUserWalletsListManager: UserWalletsListManager? = null, val wasTwinsOnboardingShownUseCase: WasTwinsOnboardingShownUseCase? = null, val saveTwinsOnboardingShownUseCase: SaveTwinsOnboardingShownUseCase? = null, val generateWalletNameUseCase: GenerateWalletNameUseCase? = null, val cardRepository: CardRepository? = null, - val tangemSdkLogger: TangemSdkLogger? = null, val settingsRepository: SettingsRepository? = null, val blockchainSDKFactory: BlockchainSDKFactory? = null, - val emailSender: EmailSender? = null, - val saveBlockchainErrorUseCase: SaveBlockchainErrorUseCase? = null, val sendFeedbackEmailUseCase: SendFeedbackEmailUseCase? = null, val getCardInfoUseCase: GetCardInfoUseCase? = null, val issuersConfigStorage: IssuersConfigStorage? = null, @@ -89,4 +68,5 @@ data class DaggerGraphState( val homeFeatureToggles: HomeFeatureToggles? = null, val getUserCountryUseCase: GetUserCountryUseCase? = null, val onrampFeatureToggles: OnrampFeatureToggles? = null, + val environmentConfigStorage: EnvironmentConfigStorage? = null, ) : StateType \ No newline at end of file From dab5fb51b66abe978596251ee5284b7413313201 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 25 Oct 2024 14:20:31 +0400 Subject: [PATCH 026/106] Updated on 2026-08-14 --- .../assets/configs/feature_toggles_config.json | 4 ++++ .../featuretoggles/WalletFeatureToggles.kt | 11 +++++++++++ .../wallet/di/WalletFeatureTogglesModule.kt | 18 ++++++++++++++++++ .../DefaultWalletFeatureToggles.kt | 13 +++++++++++++ 4 files changed, 46 insertions(+) create mode 100644 features/wallet/api/src/main/kotlin/com/tangem/features/wallet/featuretoggles/WalletFeatureToggles.kt create mode 100644 features/wallet/impl/src/main/java/com/tangem/feature/wallet/di/WalletFeatureTogglesModule.kt create mode 100644 features/wallet/impl/src/main/java/com/tangem/feature/wallet/featuretoggles/DefaultWalletFeatureToggles.kt diff --git a/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json b/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json index b906e0ce74..3c29124728 100644 --- a/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json +++ b/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json @@ -26,5 +26,9 @@ { "name": "ONRAMP_ENABLED", "version": "undefined" + }, + { + "name": "MAIN_ACTION_BUTTONS_ENABLED", + "version": "undefined" } ] diff --git a/features/wallet/api/src/main/kotlin/com/tangem/features/wallet/featuretoggles/WalletFeatureToggles.kt b/features/wallet/api/src/main/kotlin/com/tangem/features/wallet/featuretoggles/WalletFeatureToggles.kt new file mode 100644 index 0000000000..97d2d5800f --- /dev/null +++ b/features/wallet/api/src/main/kotlin/com/tangem/features/wallet/featuretoggles/WalletFeatureToggles.kt @@ -0,0 +1,11 @@ +package com.tangem.features.wallet.featuretoggles + +/** + * Wallet feature toggles + * +[REDACTED_AUTHOR] + */ +interface WalletFeatureToggles { + + val isMainActionButtonsEnabled: Boolean +} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/di/WalletFeatureTogglesModule.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/di/WalletFeatureTogglesModule.kt new file mode 100644 index 0000000000..aa51974608 --- /dev/null +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/di/WalletFeatureTogglesModule.kt @@ -0,0 +1,18 @@ +package com.tangem.feature.wallet.di + +import com.tangem.feature.wallet.featuretoggles.DefaultWalletFeatureToggles +import com.tangem.features.wallet.featuretoggles.WalletFeatureToggles +import dagger.Binds +import dagger.Module +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +internal interface WalletFeatureTogglesModule { + + @Singleton + @Binds + fun bindWalletFeatureToggles(toggles: DefaultWalletFeatureToggles): WalletFeatureToggles +} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/featuretoggles/DefaultWalletFeatureToggles.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/featuretoggles/DefaultWalletFeatureToggles.kt new file mode 100644 index 0000000000..cda1776fc1 --- /dev/null +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/featuretoggles/DefaultWalletFeatureToggles.kt @@ -0,0 +1,13 @@ +package com.tangem.feature.wallet.featuretoggles + +import com.tangem.core.featuretoggle.manager.FeatureTogglesManager +import com.tangem.features.wallet.featuretoggles.WalletFeatureToggles +import javax.inject.Inject + +internal class DefaultWalletFeatureToggles @Inject constructor( + private val featureTogglesManager: FeatureTogglesManager, +) : WalletFeatureToggles { + + override val isMainActionButtonsEnabled: Boolean + get() = featureTogglesManager.isFeatureEnabled(name = "MAIN_ACTION_BUTTONS_ENABLED") +} \ No newline at end of file From 967d5520a1749489a6b47e61ae37aa2dd23f5e61 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 25 Oct 2024 17:49:53 +0300 Subject: [PATCH 027/106] Updated on 2026-08-14 --- .../tap/di/domain/StakingDomainModule.kt | 57 ++++----- .../datasource/api/stakekit/StakeKitApi.kt | 18 ++- .../{EnterActionResponse.kt => ActionDTO.kt} | 2 +- .../models/response/GetActionsResponse.kt | 16 +++ ...ceStoreModule.kt => StakingStoreModule.kt} | 17 ++- .../datasource/di/StakingTokensStoreModule.kt | 20 --- .../local/token/DefaultStakingActionsStore.kt | 34 +++++ .../local/token/DefaultStakingYieldsStore.kt | 4 +- .../local/token/StakingActionsStore.kt | 13 ++ .../staking/DefaultStakingActionRepository.kt | 30 +++++ ...aultStakingPendingTransactionRepository.kt | 32 ----- .../data/staking/DefaultStakingRepository.kt | 44 ++++++- .../PendingTransactionItemConverter.kt | 23 ---- .../action/ActionStatusConverter.kt | 17 ++- .../action/EnterActionResponseConverter.kt | 6 +- .../data/staking/di/StakingDataModule.kt | 18 +-- .../staking/model/PendingTransaction.kt | 16 --- .../domain/staking/model/SubmitHashData.kt | 1 - .../staking/model/stakekit/YieldBalance.kt | 8 -- .../domain/staking/FetchActionsUseCase.kt | 41 ++++++ .../domain/staking/GetActionsUseCase.kt | 34 +++++ .../GetStakingPendingTransactionsUseCase.kt | 25 ---- .../InvalidatePendingTransactionsUseCase.kt | 119 ++++++++---------- .../staking/SavePendingTransactionUseCase.kt | 28 ----- .../repositories/StakingActionRepository.kt | 13 ++ .../StakingPendingTransactionRepository.kt | 14 --- .../staking/repositories/StakingRepository.kt | 9 ++ .../tokens/error/CurrencyStatusError.kt | 2 +- .../repository/MockStakingRepository.kt | 9 ++ .../impl/presentation/state/StakingUiState.kt | 2 - .../state/helpers/StakingBalanceUpdater.kt | 31 ++++- .../state/helpers/StakingTransactionSender.kt | 39 +----- .../ConfirmationStatePreviewData.kt | 1 - .../SetConfirmationStateInitTransformer.kt | 1 - ...etPossiblePendingTransactionTransformer.kt | 40 ------ .../viewmodel/StakingViewModel.kt | 71 ++++++----- .../viewmodels/TokenDetailsViewModel.kt | 5 +- 37 files changed, 452 insertions(+), 408 deletions(-) rename core/datasource/src/main/java/com/tangem/datasource/api/stakekit/models/response/{EnterActionResponse.kt => ActionDTO.kt} (97%) create mode 100644 core/datasource/src/main/java/com/tangem/datasource/api/stakekit/models/response/GetActionsResponse.kt rename core/datasource/src/main/java/com/tangem/datasource/di/{StakingBalanceStoreModule.kt => StakingStoreModule.kt} (54%) delete mode 100644 core/datasource/src/main/java/com/tangem/datasource/di/StakingTokensStoreModule.kt create mode 100644 core/datasource/src/main/java/com/tangem/datasource/local/token/DefaultStakingActionsStore.kt create mode 100644 core/datasource/src/main/java/com/tangem/datasource/local/token/StakingActionsStore.kt create mode 100644 data/staking/src/main/java/com/tangem/data/staking/DefaultStakingActionRepository.kt delete mode 100644 data/staking/src/main/java/com/tangem/data/staking/DefaultStakingPendingTransactionRepository.kt delete mode 100644 data/staking/src/main/java/com/tangem/data/staking/PendingTransactionItemConverter.kt delete mode 100644 domain/staking/models/src/main/kotlin/com/tangem/domain/staking/model/PendingTransaction.kt create mode 100644 domain/staking/src/main/java/com/tangem/domain/staking/FetchActionsUseCase.kt create mode 100644 domain/staking/src/main/java/com/tangem/domain/staking/GetActionsUseCase.kt delete mode 100644 domain/staking/src/main/java/com/tangem/domain/staking/GetStakingPendingTransactionsUseCase.kt delete mode 100644 domain/staking/src/main/java/com/tangem/domain/staking/SavePendingTransactionUseCase.kt create mode 100644 domain/staking/src/main/java/com/tangem/domain/staking/repositories/StakingActionRepository.kt delete mode 100644 domain/staking/src/main/java/com/tangem/domain/staking/repositories/StakingPendingTransactionRepository.kt delete mode 100644 features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetPossiblePendingTransactionTransformer.kt diff --git a/app/src/main/java/com/tangem/tap/di/domain/StakingDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/StakingDomainModule.kt index be10ba1059..53e9546b1b 100644 --- a/app/src/main/java/com/tangem/tap/di/domain/StakingDomainModule.kt +++ b/app/src/main/java/com/tangem/tap/di/domain/StakingDomainModule.kt @@ -1,10 +1,7 @@ package com.tangem.tap.di.domain import com.tangem.domain.staking.* -import com.tangem.domain.staking.repositories.StakingErrorResolver -import com.tangem.domain.staking.repositories.StakingPendingTransactionRepository -import com.tangem.domain.staking.repositories.StakingRepository -import com.tangem.domain.staking.repositories.StakingTransactionHashRepository +import com.tangem.domain.staking.repositories.* import dagger.Module import dagger.Provides import dagger.hilt.InstallIn @@ -51,6 +48,32 @@ internal object StakingDomainModule { ) } + @Provides + @Singleton + fun provideFetchActionsUseCase( + stakingRepository: StakingRepository, + stakingActionRepository: StakingActionRepository, + stakingErrorResolver: StakingErrorResolver, + ): FetchActionsUseCase { + return FetchActionsUseCase( + stakingRepository = stakingRepository, + stakingActionRepository = stakingActionRepository, + stakingErrorResolver = stakingErrorResolver, + ) + } + + @Provides + @Singleton + fun provideGetActionsUseCase( + stakingActionRepository: StakingActionRepository, + stakingErrorResolver: StakingErrorResolver, + ): GetActionsUseCase { + return GetActionsUseCase( + stakingActionRepository = stakingActionRepository, + stakingErrorResolver = stakingErrorResolver, + ) + } + @Provides @Singleton fun provideGetStakingTokensUseCase( @@ -123,38 +146,12 @@ internal object StakingDomainModule { ) } - @Provides - @Singleton - fun provideSavePendingTransactionUseCase( - stakingPendingTransactionRepository: StakingPendingTransactionRepository, - stakingErrorResolver: StakingErrorResolver, - ): SavePendingTransactionUseCase { - return SavePendingTransactionUseCase( - stakingPendingTransactionRepository = stakingPendingTransactionRepository, - stakingErrorResolver = stakingErrorResolver, - ) - } - @Provides @Singleton fun provideInvalidatePendingTransactionsUseCase( - stakingPendingTransactionRepository: StakingPendingTransactionRepository, stakingErrorResolver: StakingErrorResolver, ): InvalidatePendingTransactionsUseCase { return InvalidatePendingTransactionsUseCase( - stakingPendingTransactionRepository = stakingPendingTransactionRepository, - stakingErrorResolver = stakingErrorResolver, - ) - } - - @Provides - @Singleton - fun provideGetPendingTransactionsUseCase( - stakingPendingTransactionRepository: StakingPendingTransactionRepository, - stakingErrorResolver: StakingErrorResolver, - ): GetStakingPendingTransactionsUseCase { - return GetStakingPendingTransactionsUseCase( - stakingPendingTransactionRepository = stakingPendingTransactionRepository, stakingErrorResolver = stakingErrorResolver, ) } diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/stakekit/StakeKitApi.kt b/core/datasource/src/main/java/com/tangem/datasource/api/stakekit/StakeKitApi.kt index 487f53ea0e..745fa5fe39 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/stakekit/StakeKitApi.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/stakekit/StakeKitApi.kt @@ -3,7 +3,8 @@ package com.tangem.datasource.api.stakekit import com.tangem.datasource.api.common.response.ApiResponse import com.tangem.datasource.api.stakekit.models.request.* import com.tangem.datasource.api.stakekit.models.response.EnabledYieldsResponse -import com.tangem.datasource.api.stakekit.models.response.EnterActionResponse +import com.tangem.datasource.api.stakekit.models.response.ActionDTO +import com.tangem.datasource.api.stakekit.models.response.GetActionsResponse import com.tangem.datasource.api.stakekit.models.response.model.BalanceDTO import com.tangem.datasource.api.stakekit.models.response.model.YieldBalanceWrapperDTO import com.tangem.datasource.api.stakekit.models.response.model.transaction.StakingGasEstimateDTO @@ -35,14 +36,23 @@ interface StakeKitApi { @Body body: YieldBalanceRequestBody, ): ApiResponse> + @GET("actions") + suspend fun getActions( + @Query("walletAddress") walletAddress: String, + @Query("network") network: String, + @Query("status") status: String, + @Query("sort") sort: String = "createdAtDesc", + @Query("limit") limit: Int = 50, + ): ApiResponse + @POST("actions/enter") - suspend fun createEnterAction(@Body body: ActionRequestBody): ApiResponse + suspend fun createEnterAction(@Body body: ActionRequestBody): ApiResponse @POST("actions/exit") - suspend fun createExitAction(@Body body: ActionRequestBody): ApiResponse + suspend fun createExitAction(@Body body: ActionRequestBody): ApiResponse @POST("actions/pending") - suspend fun createPendingAction(@Body body: PendingActionRequestBody): ApiResponse + suspend fun createPendingAction(@Body body: PendingActionRequestBody): ApiResponse @POST("actions/enter/estimate-gas") suspend fun estimateGasOnEnter(@Body body: ActionRequestBody): ApiResponse diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/stakekit/models/response/EnterActionResponse.kt b/core/datasource/src/main/java/com/tangem/datasource/api/stakekit/models/response/ActionDTO.kt similarity index 97% rename from core/datasource/src/main/java/com/tangem/datasource/api/stakekit/models/response/EnterActionResponse.kt rename to core/datasource/src/main/java/com/tangem/datasource/api/stakekit/models/response/ActionDTO.kt index 9413c43398..87382ee7ef 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/stakekit/models/response/EnterActionResponse.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/stakekit/models/response/ActionDTO.kt @@ -9,7 +9,7 @@ import org.joda.time.DateTime import java.math.BigDecimal @JsonClass(generateAdapter = true) -data class EnterActionResponse( +data class ActionDTO( @Json(name = "id") val id: String, @Json(name = "integrationId") diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/stakekit/models/response/GetActionsResponse.kt b/core/datasource/src/main/java/com/tangem/datasource/api/stakekit/models/response/GetActionsResponse.kt new file mode 100644 index 0000000000..0e116d42be --- /dev/null +++ b/core/datasource/src/main/java/com/tangem/datasource/api/stakekit/models/response/GetActionsResponse.kt @@ -0,0 +1,16 @@ +package com.tangem.datasource.api.stakekit.models.response + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +@JsonClass(generateAdapter = true) +data class GetActionsResponse( + @Json(name = "data") + val data: List, + @Json(name = "hasNextPage") + val hasNextPage: Boolean, + @Json(name = "limit") + val limit: Int, + @Json(name = "page") + val page: Int, +) \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/di/StakingBalanceStoreModule.kt b/core/datasource/src/main/java/com/tangem/datasource/di/StakingStoreModule.kt similarity index 54% rename from core/datasource/src/main/java/com/tangem/datasource/di/StakingBalanceStoreModule.kt rename to core/datasource/src/main/java/com/tangem/datasource/di/StakingStoreModule.kt index 21d78318c1..3dda78cc57 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/di/StakingBalanceStoreModule.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/di/StakingStoreModule.kt @@ -1,8 +1,9 @@ package com.tangem.datasource.di import com.tangem.datasource.local.datastore.RuntimeDataStore +import com.tangem.datasource.local.token.* import com.tangem.datasource.local.token.DefaultStakingBalanceStore -import com.tangem.datasource.local.token.StakingBalanceStore +import com.tangem.datasource.local.token.DefaultStakingYieldsStore import dagger.Module import dagger.Provides import dagger.hilt.InstallIn @@ -11,11 +12,23 @@ import javax.inject.Singleton @Module @InstallIn(SingletonComponent::class) -internal object StakingBalanceStoreModule { +internal object StakingStoreModule { + + @Provides + @Singleton + fun provideStakingTokensStore(): StakingYieldsStore { + return DefaultStakingYieldsStore() + } @Provides @Singleton fun provideStakingBalanceStore(): StakingBalanceStore { return DefaultStakingBalanceStore(dataStore = RuntimeDataStore()) } + + @Provides + @Singleton + fun provideStakingActionsStore(): StakingActionsStore { + return DefaultStakingActionsStore(dataStore = RuntimeDataStore()) + } } \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/di/StakingTokensStoreModule.kt b/core/datasource/src/main/java/com/tangem/datasource/di/StakingTokensStoreModule.kt deleted file mode 100644 index d55c4066ec..0000000000 --- a/core/datasource/src/main/java/com/tangem/datasource/di/StakingTokensStoreModule.kt +++ /dev/null @@ -1,20 +0,0 @@ -package com.tangem.datasource.di - -import com.tangem.datasource.local.token.DefaultStakingYieldsStore -import com.tangem.datasource.local.token.StakingYieldsStore -import dagger.Module -import dagger.Provides -import dagger.hilt.InstallIn -import dagger.hilt.components.SingletonComponent -import javax.inject.Singleton - -@Module -@InstallIn(SingletonComponent::class) -internal object StakingTokensStoreModule { - - @Provides - @Singleton - fun provideStakingTokensStore(): StakingYieldsStore { - return DefaultStakingYieldsStore() - } -} \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/token/DefaultStakingActionsStore.kt b/core/datasource/src/main/java/com/tangem/datasource/local/token/DefaultStakingActionsStore.kt new file mode 100644 index 0000000000..39f499ca68 --- /dev/null +++ b/core/datasource/src/main/java/com/tangem/datasource/local/token/DefaultStakingActionsStore.kt @@ -0,0 +1,34 @@ +package com.tangem.datasource.local.token + +import com.tangem.datasource.local.datastore.core.StringKeyDataStore +import com.tangem.domain.staking.model.stakekit.action.StakingAction +import com.tangem.domain.tokens.model.CryptoCurrency +import com.tangem.domain.wallets.models.UserWalletId +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock + +internal class DefaultStakingActionsStore( + private val dataStore: StringKeyDataStore>, +) : StakingActionsStore { + + private val mutex = Mutex() + + override fun get(userWalletId: UserWalletId, cryptoCurrencyId: CryptoCurrency.ID): Flow> { + return dataStore.get(composeKey(userWalletId, cryptoCurrencyId)) + } + + override suspend fun store( + userWalletId: UserWalletId, + cryptoCurrencyId: CryptoCurrency.ID, + items: List, + ) { + mutex.withLock { + dataStore.store(composeKey(userWalletId, cryptoCurrencyId), items) + } + } + + private fun composeKey(userWalletId: UserWalletId, cryptoCurrencyId: CryptoCurrency.ID): String { + return userWalletId.stringValue + cryptoCurrencyId.value + } +} \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/token/DefaultStakingYieldsStore.kt b/core/datasource/src/main/java/com/tangem/datasource/local/token/DefaultStakingYieldsStore.kt index db8de27d38..d30a83e4c3 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/local/token/DefaultStakingYieldsStore.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/local/token/DefaultStakingYieldsStore.kt @@ -4,13 +4,13 @@ import com.tangem.datasource.api.stakekit.models.response.model.YieldDTO internal class DefaultStakingYieldsStore : StakingYieldsStore { - private var yields = mutableListOf() + private var yields = listOf() override fun get(): List { return yields } override fun store(items: List) { - yields = items.toMutableList() + yields = items } } \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/token/StakingActionsStore.kt b/core/datasource/src/main/java/com/tangem/datasource/local/token/StakingActionsStore.kt new file mode 100644 index 0000000000..aa6de02da4 --- /dev/null +++ b/core/datasource/src/main/java/com/tangem/datasource/local/token/StakingActionsStore.kt @@ -0,0 +1,13 @@ +package com.tangem.datasource.local.token + +import com.tangem.domain.staking.model.stakekit.action.StakingAction +import com.tangem.domain.tokens.model.CryptoCurrency +import com.tangem.domain.wallets.models.UserWalletId +import kotlinx.coroutines.flow.Flow + +interface StakingActionsStore { + + fun get(userWalletId: UserWalletId, cryptoCurrencyId: CryptoCurrency.ID): Flow> + + suspend fun store(userWalletId: UserWalletId, cryptoCurrencyId: CryptoCurrency.ID, items: List) +} \ No newline at end of file diff --git a/data/staking/src/main/java/com/tangem/data/staking/DefaultStakingActionRepository.kt b/data/staking/src/main/java/com/tangem/data/staking/DefaultStakingActionRepository.kt new file mode 100644 index 0000000000..888f6a342c --- /dev/null +++ b/data/staking/src/main/java/com/tangem/data/staking/DefaultStakingActionRepository.kt @@ -0,0 +1,30 @@ +package com.tangem.data.staking + +import com.tangem.datasource.local.token.StakingActionsStore +import com.tangem.domain.staking.model.stakekit.action.StakingAction +import com.tangem.domain.staking.repositories.StakingActionRepository +import com.tangem.domain.tokens.model.CryptoCurrency +import com.tangem.domain.wallets.models.UserWalletId +import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.withContext + +internal class DefaultStakingActionRepository( + private val stakingActionsStore: StakingActionsStore, + private val dispatchers: CoroutineDispatcherProvider, +) : StakingActionRepository { + + override suspend fun store( + userWalletId: UserWalletId, + cryptoCurrencyId: CryptoCurrency.ID, + actions: List, + ) { + withContext(dispatchers.io) { + stakingActionsStore.store(userWalletId, cryptoCurrencyId, actions) + } + } + + override fun get(userWalletId: UserWalletId, cryptoCurrencyId: CryptoCurrency.ID): Flow> { + return stakingActionsStore.get(userWalletId, cryptoCurrencyId) + } +} \ No newline at end of file diff --git a/data/staking/src/main/java/com/tangem/data/staking/DefaultStakingPendingTransactionRepository.kt b/data/staking/src/main/java/com/tangem/data/staking/DefaultStakingPendingTransactionRepository.kt deleted file mode 100644 index 008ea7161a..0000000000 --- a/data/staking/src/main/java/com/tangem/data/staking/DefaultStakingPendingTransactionRepository.kt +++ /dev/null @@ -1,32 +0,0 @@ -package com.tangem.data.staking - -import com.tangem.domain.staking.model.PendingTransaction -import com.tangem.domain.staking.model.stakekit.BalanceItem -import com.tangem.domain.staking.repositories.StakingPendingTransactionRepository -import com.tangem.domain.wallets.models.UserWalletId -import java.util.concurrent.ConcurrentHashMap -import java.util.concurrent.CopyOnWriteArrayList - -internal class DefaultStakingPendingTransactionRepository : StakingPendingTransactionRepository { - - private val pendingTransactionsMap = ConcurrentHashMap>() - - override fun saveTransaction(userWalletId: UserWalletId, transaction: PendingTransaction) { - val transactions = pendingTransactionsMap.computeIfAbsent(userWalletId) { CopyOnWriteArrayList() } - transactions.add(transaction) - } - - override fun removeTransactions(userWalletId: UserWalletId, transactions: Set) { - pendingTransactionsMap[userWalletId]?.removeAll(transactions) - } - - override fun getTransactionsWithBalanceItems( - userWalletId: UserWalletId, - ): List> { - return pendingTransactionsMap[userWalletId]?.mapNotNull { pendingTransaction: PendingTransaction -> - PendingTransactionItemConverter.convert(pendingTransaction)?.let { balanceItem -> - pendingTransaction to balanceItem - } - } ?: emptyList() - } -} \ No newline at end of file diff --git a/data/staking/src/main/java/com/tangem/data/staking/DefaultStakingRepository.kt b/data/staking/src/main/java/com/tangem/data/staking/DefaultStakingRepository.kt index ce9296103a..e57c15dd67 100644 --- a/data/staking/src/main/java/com/tangem/data/staking/DefaultStakingRepository.kt +++ b/data/staking/src/main/java/com/tangem/data/staking/DefaultStakingRepository.kt @@ -25,19 +25,19 @@ import com.tangem.data.staking.converters.transaction.StakingTransactionTypeConv import com.tangem.datasource.api.common.response.getOrThrow import com.tangem.datasource.api.stakekit.StakeKitApi import com.tangem.datasource.api.stakekit.models.request.* +import com.tangem.datasource.api.stakekit.models.response.model.NetworkTypeDTO import com.tangem.datasource.api.stakekit.models.response.model.YieldBalanceWrapperDTO +import com.tangem.datasource.api.stakekit.models.response.model.action.StakingActionStatusDTO import com.tangem.datasource.api.stakekit.models.response.model.transaction.tron.TronStakeKitTransaction import com.tangem.datasource.local.token.StakingBalanceStore import com.tangem.datasource.local.token.StakingYieldsStore import com.tangem.domain.staking.model.StakingApproval import com.tangem.domain.staking.model.StakingAvailability import com.tangem.domain.staking.model.StakingEntryInfo -import com.tangem.domain.staking.model.stakekit.NetworkType -import com.tangem.domain.staking.model.stakekit.Yield -import com.tangem.domain.staking.model.stakekit.YieldBalance -import com.tangem.domain.staking.model.stakekit.YieldBalanceList +import com.tangem.domain.staking.model.stakekit.* import com.tangem.domain.staking.model.stakekit.action.StakingAction import com.tangem.domain.staking.model.stakekit.action.StakingActionCommonType +import com.tangem.domain.staking.model.stakekit.action.StakingActionStatus import com.tangem.domain.staking.model.stakekit.action.StakingActionType import com.tangem.domain.staking.model.stakekit.transaction.ActionParams import com.tangem.domain.staking.model.stakekit.transaction.StakingGasEstimate @@ -102,6 +102,8 @@ internal class DefaultStakingRepository( private val yieldBalanceListConverter = YieldBalanceListConverter(yieldBalanceConverter) private val tronStakeKitTransactionAdapter by lazy { moshi.adapter(TronStakeKitTransaction::class.java) } + private val networkTypeAdapter by lazy { moshi.adapter(NetworkTypeDTO::class.java) } + private val stakingActionStatusAdapter by lazy { moshi.adapter(StakingActionStatusDTO::class.java) } override fun getIntegrationKey(cryptoCurrencyId: CryptoCurrency.ID): String = with(cryptoCurrencyId) { rawNetworkId.plus(rawCurrencyId) @@ -140,6 +142,40 @@ internal class DefaultStakingRepository( } } + override suspend fun getActions( + userWalletId: UserWalletId, + cryptoCurrency: CryptoCurrency, + networkType: NetworkType, + stakingActionStatus: StakingActionStatus, + ): List { + return withContext(dispatchers.io) { + val address = walletManagersFacade.getDefaultAddress(userWalletId, cryptoCurrency.network).orEmpty() + + val networkTypeDto = networkTypeConverter.convertBack(networkType) + val networkTypeString = networkTypeDto.extractJsonName() + + val actionStatusDTO = actionStatusConverter.convertBack(stakingActionStatus) + val actionStatusString = actionStatusDTO.extractJsonName() + + enterActionResponseConverter.convertListIgnoreErrors( + input = stakeKitApi.getActions( + walletAddress = address, + network = networkTypeString, + status = actionStatusString, + ).getOrThrow().data, + onError = { Timber.e("Error converting staking actions list: $it") }, + ) + } + } + + private fun NetworkTypeDTO.extractJsonName(): String { + return networkTypeAdapter.toJson(this).replace("\"", "") + } + + private fun StakingActionStatusDTO.extractJsonName(): String { + return stakingActionStatusAdapter.toJson(this).replace("\"", "") + } + override suspend fun getEntryInfo(cryptoCurrencyId: CryptoCurrency.ID, symbol: String): StakingEntryInfo { return withContext(dispatchers.io) { val yield = getYield(cryptoCurrencyId, symbol) diff --git a/data/staking/src/main/java/com/tangem/data/staking/PendingTransactionItemConverter.kt b/data/staking/src/main/java/com/tangem/data/staking/PendingTransactionItemConverter.kt deleted file mode 100644 index 026c1435bd..0000000000 --- a/data/staking/src/main/java/com/tangem/data/staking/PendingTransactionItemConverter.kt +++ /dev/null @@ -1,23 +0,0 @@ -package com.tangem.data.staking - -import com.tangem.domain.staking.model.PendingTransaction -import com.tangem.domain.staking.model.stakekit.BalanceItem -import com.tangem.utils.converter.Converter -import org.joda.time.DateTime - -internal object PendingTransactionItemConverter : Converter { - - override fun convert(value: PendingTransaction): BalanceItem? { - return BalanceItem( - groupId = value.groupId ?: return null, - token = value.token, - type = value.type, - amount = value.amount, - rawCurrencyId = value.rawCurrencyId, - validatorAddress = value.validator?.address, - date = DateTime.now(), - pendingActions = emptyList(), - isPending = true, - ) - } -} \ No newline at end of file diff --git a/data/staking/src/main/java/com/tangem/data/staking/converters/action/ActionStatusConverter.kt b/data/staking/src/main/java/com/tangem/data/staking/converters/action/ActionStatusConverter.kt index 4bc04836a3..0a4ce1e6ff 100644 --- a/data/staking/src/main/java/com/tangem/data/staking/converters/action/ActionStatusConverter.kt +++ b/data/staking/src/main/java/com/tangem/data/staking/converters/action/ActionStatusConverter.kt @@ -2,9 +2,10 @@ package com.tangem.data.staking.converters.action import com.tangem.datasource.api.stakekit.models.response.model.action.StakingActionStatusDTO import com.tangem.domain.staking.model.stakekit.action.StakingActionStatus -import com.tangem.utils.converter.Converter +import com.tangem.utils.converter.TwoWayConverter + +class ActionStatusConverter : TwoWayConverter { -class ActionStatusConverter : Converter { override fun convert(value: StakingActionStatusDTO): StakingActionStatus { return when (value) { StakingActionStatusDTO.CANCELED -> StakingActionStatus.CANCELED @@ -16,4 +17,16 @@ class ActionStatusConverter : Converter StakingActionStatus.UNKNOWN } } + + override fun convertBack(value: StakingActionStatus): StakingActionStatusDTO { + return when (value) { + StakingActionStatus.CANCELED -> StakingActionStatusDTO.CANCELED + StakingActionStatus.CREATED -> StakingActionStatusDTO.CREATED + StakingActionStatus.WAITING_FOR_NEXT -> StakingActionStatusDTO.WAITING_FOR_NEXT + StakingActionStatus.PROCESSING -> StakingActionStatusDTO.PROCESSING + StakingActionStatus.FAILED -> StakingActionStatusDTO.FAILED + StakingActionStatus.SUCCESS -> StakingActionStatusDTO.SUCCESS + else -> StakingActionStatusDTO.UNKNOWN + } + } } \ No newline at end of file diff --git a/data/staking/src/main/java/com/tangem/data/staking/converters/action/EnterActionResponseConverter.kt b/data/staking/src/main/java/com/tangem/data/staking/converters/action/EnterActionResponseConverter.kt index d06ab798d2..b46fd75f85 100644 --- a/data/staking/src/main/java/com/tangem/data/staking/converters/action/EnterActionResponseConverter.kt +++ b/data/staking/src/main/java/com/tangem/data/staking/converters/action/EnterActionResponseConverter.kt @@ -1,7 +1,7 @@ package com.tangem.data.staking.converters.action import com.tangem.data.staking.converters.transaction.StakingTransactionConverter -import com.tangem.datasource.api.stakekit.models.response.EnterActionResponse +import com.tangem.datasource.api.stakekit.models.response.ActionDTO import com.tangem.domain.staking.model.stakekit.action.StakingAction import com.tangem.utils.converter.Converter @@ -9,9 +9,9 @@ class EnterActionResponseConverter( private val actionStatusConverter: ActionStatusConverter, private val stakingActionTypeConverter: StakingActionTypeConverter, private val transactionConverter: StakingTransactionConverter, -) : Converter { +) : Converter { - override fun convert(value: EnterActionResponse): StakingAction { + override fun convert(value: ActionDTO): StakingAction { return StakingAction( id = value.id, integrationId = value.integrationId, diff --git a/data/staking/src/main/java/com/tangem/data/staking/di/StakingDataModule.kt b/data/staking/src/main/java/com/tangem/data/staking/di/StakingDataModule.kt index e440b5c172..2226847178 100644 --- a/data/staking/src/main/java/com/tangem/data/staking/di/StakingDataModule.kt +++ b/data/staking/src/main/java/com/tangem/data/staking/di/StakingDataModule.kt @@ -2,8 +2,8 @@ package com.tangem.data.staking.di import com.squareup.moshi.Moshi import com.tangem.data.common.cache.CacheRegistry +import com.tangem.data.staking.* import com.tangem.data.staking.DefaultStakingErrorResolver -import com.tangem.data.staking.DefaultStakingPendingTransactionRepository import com.tangem.data.staking.DefaultStakingRepository import com.tangem.data.staking.DefaultStakingTransactionHashRepository import com.tangem.data.staking.converters.error.StakeKitErrorConverter @@ -11,12 +11,10 @@ import com.tangem.datasource.api.stakekit.StakeKitApi import com.tangem.datasource.api.stakekit.models.response.model.error.StakeKitErrorResponse import com.tangem.datasource.di.NetworkMoshi import com.tangem.datasource.local.preferences.AppPreferencesStore +import com.tangem.datasource.local.token.StakingActionsStore import com.tangem.datasource.local.token.StakingBalanceStore import com.tangem.datasource.local.token.StakingYieldsStore -import com.tangem.domain.staking.repositories.StakingErrorResolver -import com.tangem.domain.staking.repositories.StakingPendingTransactionRepository -import com.tangem.domain.staking.repositories.StakingRepository -import com.tangem.domain.staking.repositories.StakingTransactionHashRepository +import com.tangem.domain.staking.repositories.* import com.tangem.domain.walletmanager.WalletManagersFacade import com.tangem.domain.wallets.usecase.GetUserWalletUseCase import com.tangem.features.staking.api.featuretoggles.StakingFeatureToggles @@ -73,8 +71,14 @@ internal object StakingDataModule { @Provides @Singleton - fun provideStakingPendingTransactionRepository(): StakingPendingTransactionRepository { - return DefaultStakingPendingTransactionRepository() + fun provideStakingActionRepository( + stakingActionsStore: StakingActionsStore, + dispatchers: CoroutineDispatcherProvider, + ): StakingActionRepository { + return DefaultStakingActionRepository( + stakingActionsStore = stakingActionsStore, + dispatchers = dispatchers, + ) } @Provides diff --git a/domain/staking/models/src/main/kotlin/com/tangem/domain/staking/model/PendingTransaction.kt b/domain/staking/models/src/main/kotlin/com/tangem/domain/staking/model/PendingTransaction.kt deleted file mode 100644 index 408de7dc0d..0000000000 --- a/domain/staking/models/src/main/kotlin/com/tangem/domain/staking/model/PendingTransaction.kt +++ /dev/null @@ -1,16 +0,0 @@ -package com.tangem.domain.staking.model - -import com.tangem.domain.staking.model.stakekit.BalanceType -import com.tangem.domain.staking.model.stakekit.Token -import com.tangem.domain.staking.model.stakekit.Yield -import java.math.BigDecimal - -data class PendingTransaction( - val groupId: String?, - val token: Token, - val type: BalanceType, - val amount: BigDecimal, - val rawCurrencyId: String?, - val validator: Yield.Validator?, - val balancesId: Int, -) \ No newline at end of file diff --git a/domain/staking/models/src/main/kotlin/com/tangem/domain/staking/model/SubmitHashData.kt b/domain/staking/models/src/main/kotlin/com/tangem/domain/staking/model/SubmitHashData.kt index 684264334d..662c27c5c8 100644 --- a/domain/staking/models/src/main/kotlin/com/tangem/domain/staking/model/SubmitHashData.kt +++ b/domain/staking/models/src/main/kotlin/com/tangem/domain/staking/model/SubmitHashData.kt @@ -3,5 +3,4 @@ package com.tangem.domain.staking.model data class SubmitHashData( val transactionHash: String, val transactionId: String, - val pendingTransaction: PendingTransaction, ) \ No newline at end of file diff --git a/domain/staking/models/src/main/kotlin/com/tangem/domain/staking/model/stakekit/YieldBalance.kt b/domain/staking/models/src/main/kotlin/com/tangem/domain/staking/model/stakekit/YieldBalance.kt index d1f0f3a3f3..ef9946cdb7 100644 --- a/domain/staking/models/src/main/kotlin/com/tangem/domain/staking/model/stakekit/YieldBalance.kt +++ b/domain/staking/models/src/main/kotlin/com/tangem/domain/staking/model/stakekit/YieldBalance.kt @@ -32,14 +32,6 @@ sealed class YieldBalance { .distinctBy { it.validatorAddress } .size } - - fun getBalancesUniqueId(): Int { - // need to exclude rewards because their amount may change frequently - return balance.items - .filter { it.type != BalanceType.REWARDS } - .map { it.amount.toString() + it.type.toString() + it.groupId } - .hashCode() - } } data object Empty : YieldBalance() diff --git a/domain/staking/src/main/java/com/tangem/domain/staking/FetchActionsUseCase.kt b/domain/staking/src/main/java/com/tangem/domain/staking/FetchActionsUseCase.kt new file mode 100644 index 0000000000..1e437878e6 --- /dev/null +++ b/domain/staking/src/main/java/com/tangem/domain/staking/FetchActionsUseCase.kt @@ -0,0 +1,41 @@ +package com.tangem.domain.staking + +import arrow.core.Either +import com.tangem.domain.staking.model.stakekit.NetworkType +import com.tangem.domain.staking.model.stakekit.StakingError +import com.tangem.domain.staking.model.stakekit.action.StakingActionStatus +import com.tangem.domain.staking.repositories.StakingActionRepository +import com.tangem.domain.staking.repositories.StakingErrorResolver +import com.tangem.domain.staking.repositories.StakingRepository +import com.tangem.domain.tokens.model.CryptoCurrency +import com.tangem.domain.wallets.models.UserWalletId + +/** + * Use case for getting pending actions list. + */ +class FetchActionsUseCase( + private val stakingRepository: StakingRepository, + private val stakingActionRepository: StakingActionRepository, + private val stakingErrorResolver: StakingErrorResolver, +) { + + suspend operator fun invoke( + userWalletId: UserWalletId, + cryptoCurrency: CryptoCurrency, + networkType: NetworkType, + stakingActionStatus: StakingActionStatus, + ): Either { + return Either + .catch { + val actions = stakingRepository.getActions( + userWalletId = userWalletId, + cryptoCurrency = cryptoCurrency, + networkType = networkType, + stakingActionStatus = stakingActionStatus, + ) + + stakingActionRepository.store(userWalletId, cryptoCurrency.id, actions) + } + .mapLeft { stakingErrorResolver.resolve(it) } + } +} \ No newline at end of file diff --git a/domain/staking/src/main/java/com/tangem/domain/staking/GetActionsUseCase.kt b/domain/staking/src/main/java/com/tangem/domain/staking/GetActionsUseCase.kt new file mode 100644 index 0000000000..3190d22711 --- /dev/null +++ b/domain/staking/src/main/java/com/tangem/domain/staking/GetActionsUseCase.kt @@ -0,0 +1,34 @@ +package com.tangem.domain.staking + +import arrow.core.Either +import arrow.core.left +import arrow.core.right +import com.tangem.domain.core.utils.EitherFlow +import com.tangem.domain.staking.model.stakekit.StakingError +import com.tangem.domain.staking.model.stakekit.action.StakingAction +import com.tangem.domain.staking.repositories.StakingActionRepository +import com.tangem.domain.staking.repositories.StakingErrorResolver +import com.tangem.domain.tokens.model.CryptoCurrency +import com.tangem.domain.wallets.models.UserWalletId +import kotlinx.coroutines.flow.catch +import kotlinx.coroutines.flow.map + +/** + * Use case for getting pending actions list. + */ +class GetActionsUseCase( + private val stakingActionRepository: StakingActionRepository, + private val stakingErrorResolver: StakingErrorResolver, +) { + + operator fun invoke( + userWalletId: UserWalletId, + cryptoCurrencyId: CryptoCurrency.ID, + ): EitherFlow> { + return stakingActionRepository.get( + userWalletId = userWalletId, + cryptoCurrencyId = cryptoCurrencyId, + ).map, Either>> { it.right() } + .catch { emit(stakingErrorResolver.resolve(it).left()) } + } +} \ No newline at end of file diff --git a/domain/staking/src/main/java/com/tangem/domain/staking/GetStakingPendingTransactionsUseCase.kt b/domain/staking/src/main/java/com/tangem/domain/staking/GetStakingPendingTransactionsUseCase.kt deleted file mode 100644 index 15557232b0..0000000000 --- a/domain/staking/src/main/java/com/tangem/domain/staking/GetStakingPendingTransactionsUseCase.kt +++ /dev/null @@ -1,25 +0,0 @@ -package com.tangem.domain.staking - -import arrow.core.Either -import com.tangem.domain.staking.model.stakekit.BalanceItem -import com.tangem.domain.staking.model.stakekit.StakingError -import com.tangem.domain.staking.repositories.StakingErrorResolver -import com.tangem.domain.staking.repositories.StakingPendingTransactionRepository -import com.tangem.domain.wallets.models.UserWalletId - -/** - * Use case for getting saved staking pending transactions. - */ -class GetStakingPendingTransactionsUseCase( - private val stakingPendingTransactionRepository: StakingPendingTransactionRepository, - private val stakingErrorResolver: StakingErrorResolver, -) { - - operator fun invoke(userWalletId: UserWalletId): Either> { - return Either.catch { - stakingPendingTransactionRepository.getTransactionsWithBalanceItems(userWalletId).map { it.second } - }.mapLeft { - stakingErrorResolver.resolve(it) - } - } -} \ No newline at end of file diff --git a/domain/staking/src/main/java/com/tangem/domain/staking/InvalidatePendingTransactionsUseCase.kt b/domain/staking/src/main/java/com/tangem/domain/staking/InvalidatePendingTransactionsUseCase.kt index 2c07ae8ea0..1b0f6b9128 100644 --- a/domain/staking/src/main/java/com/tangem/domain/staking/InvalidatePendingTransactionsUseCase.kt +++ b/domain/staking/src/main/java/com/tangem/domain/staking/InvalidatePendingTransactionsUseCase.kt @@ -1,104 +1,85 @@ package com.tangem.domain.staking import arrow.core.Either -import com.tangem.domain.staking.model.PendingTransaction import com.tangem.domain.staking.model.stakekit.BalanceItem import com.tangem.domain.staking.model.stakekit.BalanceType import com.tangem.domain.staking.model.stakekit.StakingError +import com.tangem.domain.staking.model.stakekit.action.StakingAction +import com.tangem.domain.staking.model.stakekit.action.StakingActionType import com.tangem.domain.staking.repositories.StakingErrorResolver -import com.tangem.domain.staking.repositories.StakingPendingTransactionRepository -import com.tangem.domain.wallets.models.UserWalletId -import org.joda.time.DateTime -import java.math.BigDecimal import java.util.UUID class InvalidatePendingTransactionsUseCase( - private val stakingPendingTransactionRepository: StakingPendingTransactionRepository, private val stakingErrorResolver: StakingErrorResolver, ) { operator fun invoke( - userWalletId: UserWalletId, balanceItems: List, - balancesId: Int, + processingActions: List, ): Either> { return Either.catch { - val (balancesToDisplay, transactionsToRemove) = mergeRealAndPendingTransactions( - realData = balanceItems, - newBalancesId = balancesId, - pendingData = stakingPendingTransactionRepository.getTransactionsWithBalanceItems(userWalletId), + val balancesToDisplay = mergeBalancesAndProcessingActions( + realBalances = balanceItems, + processingActions = processingActions, ) - - stakingPendingTransactionRepository.removeTransactions(userWalletId, transactionsToRemove.toSet()) - balancesToDisplay }.mapLeft { stakingErrorResolver.resolve(it) } } - private fun mergeRealAndPendingTransactions( - realData: List, - newBalancesId: Int, - pendingData: List>, - ): Pair, List> { - val balances = realData.groupBy { BalanceIdentity(it.groupId, it.type, it.amount, it.date) } - .mapValues { it.value.toMutableList() } - .toMutableMap() + private fun mergeBalancesAndProcessingActions( + realBalances: List, + processingActions: List, + ): List { + val balances = realBalances.toMutableList() - val transactionsToRemove = mutableListOf() - - pendingData.forEach { (pendingTransaction, balanceItem) -> - val key = BalanceIdentity(balanceItem.groupId, balanceItem.type, balanceItem.amount, balanceItem.date) - val oldBalancesId = pendingTransaction.balancesId - - when { - newBalancesId != oldBalancesId -> { - transactionsToRemove.add(pendingTransaction) + processingActions.forEach { action -> + when (action.type) { + StakingActionType.STAKE, StakingActionType.VOTE, StakingActionType.VOTE_LOCKED -> { + processEnterAction(balances, action) } - balances.containsKey(key) -> { - val removed = balances[key]?.removeIf { !it.isPending } ?: false - if (removed) { - balances[key]?.add(balanceItem) - } + StakingActionType.WITHDRAW -> { + modifyBalancesByStatus(balances, action, BalanceType.UNSTAKED) + } + StakingActionType.UNLOCK_LOCKED -> { + modifyBalancesByStatus(balances, action, BalanceType.LOCKED) + } + StakingActionType.UNSTAKE -> { + modifyBalancesByStatus(balances, action, BalanceType.STAKED) } else -> { - val groupId = UUID.randomUUID().toString() - val now = DateTime.now() - - balances[BalanceIdentity(groupId, BalanceType.STAKED, pendingTransaction.amount, now)] = - mutableListOf( - BalanceItem( - groupId = groupId, - type = pendingTransaction.type, - amount = pendingTransaction.amount, - rawCurrencyId = pendingTransaction.rawCurrencyId, - validatorAddress = pendingTransaction.validator?.address, - date = null, - pendingActions = emptyList(), - token = pendingTransaction.token, - isPending = true, - ), - ) - - balances.entries.find { - it.key.type == BalanceType.STAKED && - it.key.amount == pendingTransaction.amount && - !it.value.any { it.isPending } - } - ?.key - ?.let { balances.remove(it) } + // intentionally do nothing } } } - return balances.values.flatten() to transactionsToRemove + return balances } - private data class BalanceIdentity( - val groupId: String, - val type: BalanceType, - val amount: BigDecimal, - val date: DateTime?, - ) + private fun processEnterAction(balances: MutableList, action: StakingAction) { + balances.add( + BalanceItem( + groupId = UUID.randomUUID().toString(), + token = balances[0].token, + type = BalanceType.STAKED, + amount = action.amount, + rawCurrencyId = null, + validatorAddress = action.validatorAddress ?: action.validatorAddresses?.get(0) ?: "", + date = null, + pendingActions = emptyList(), + isPending = true, + ), + ) + } + + private fun modifyBalancesByStatus(balances: MutableList, action: StakingAction, type: BalanceType) { + val index = balances.indexOfFirst { + !it.isPending && it.amount == action.amount && it.type == type + } + + if (index != -1) { + balances[index] = balances[index].copy(isPending = true) + } + } } \ No newline at end of file diff --git a/domain/staking/src/main/java/com/tangem/domain/staking/SavePendingTransactionUseCase.kt b/domain/staking/src/main/java/com/tangem/domain/staking/SavePendingTransactionUseCase.kt deleted file mode 100644 index ef80e06e64..0000000000 --- a/domain/staking/src/main/java/com/tangem/domain/staking/SavePendingTransactionUseCase.kt +++ /dev/null @@ -1,28 +0,0 @@ -package com.tangem.domain.staking - -import arrow.core.Either -import com.tangem.domain.staking.model.PendingTransaction -import com.tangem.domain.staking.model.stakekit.StakingError -import com.tangem.domain.staking.repositories.StakingErrorResolver -import com.tangem.domain.staking.repositories.StakingPendingTransactionRepository -import com.tangem.domain.wallets.models.UserWalletId - -/** - * Use case for saving hash that failed to submit during staking confirmation - */ -class SavePendingTransactionUseCase( - private val stakingPendingTransactionRepository: StakingPendingTransactionRepository, - private val stakingErrorResolver: StakingErrorResolver, -) { - - operator fun invoke(userWalletId: UserWalletId, transaction: PendingTransaction): Either { - return Either.catch { - stakingPendingTransactionRepository.saveTransaction( - userWalletId = userWalletId, - transaction = transaction, - ) - }.mapLeft { - stakingErrorResolver.resolve(it) - } - } -} \ No newline at end of file diff --git a/domain/staking/src/main/java/com/tangem/domain/staking/repositories/StakingActionRepository.kt b/domain/staking/src/main/java/com/tangem/domain/staking/repositories/StakingActionRepository.kt new file mode 100644 index 0000000000..142c9d08f6 --- /dev/null +++ b/domain/staking/src/main/java/com/tangem/domain/staking/repositories/StakingActionRepository.kt @@ -0,0 +1,13 @@ +package com.tangem.domain.staking.repositories + +import com.tangem.domain.staking.model.stakekit.action.StakingAction +import com.tangem.domain.tokens.model.CryptoCurrency +import com.tangem.domain.wallets.models.UserWalletId +import kotlinx.coroutines.flow.Flow + +interface StakingActionRepository { + + suspend fun store(userWalletId: UserWalletId, cryptoCurrencyId: CryptoCurrency.ID, actions: List) + + fun get(userWalletId: UserWalletId, cryptoCurrencyId: CryptoCurrency.ID): Flow> +} \ No newline at end of file diff --git a/domain/staking/src/main/java/com/tangem/domain/staking/repositories/StakingPendingTransactionRepository.kt b/domain/staking/src/main/java/com/tangem/domain/staking/repositories/StakingPendingTransactionRepository.kt deleted file mode 100644 index 708b8e1352..0000000000 --- a/domain/staking/src/main/java/com/tangem/domain/staking/repositories/StakingPendingTransactionRepository.kt +++ /dev/null @@ -1,14 +0,0 @@ -package com.tangem.domain.staking.repositories - -import com.tangem.domain.staking.model.PendingTransaction -import com.tangem.domain.staking.model.stakekit.BalanceItem -import com.tangem.domain.wallets.models.UserWalletId - -interface StakingPendingTransactionRepository { - - fun getTransactionsWithBalanceItems(userWalletId: UserWalletId): List> - - fun saveTransaction(userWalletId: UserWalletId, transaction: PendingTransaction) - - fun removeTransactions(userWalletId: UserWalletId, transactions: Set) -} \ No newline at end of file diff --git a/domain/staking/src/main/java/com/tangem/domain/staking/repositories/StakingRepository.kt b/domain/staking/src/main/java/com/tangem/domain/staking/repositories/StakingRepository.kt index 568683be77..e68adf73a9 100644 --- a/domain/staking/src/main/java/com/tangem/domain/staking/repositories/StakingRepository.kt +++ b/domain/staking/src/main/java/com/tangem/domain/staking/repositories/StakingRepository.kt @@ -6,10 +6,12 @@ import com.tangem.blockchain.common.transaction.Fee import com.tangem.domain.staking.model.StakingApproval import com.tangem.domain.staking.model.StakingAvailability import com.tangem.domain.staking.model.StakingEntryInfo +import com.tangem.domain.staking.model.stakekit.NetworkType import com.tangem.domain.staking.model.stakekit.Yield import com.tangem.domain.staking.model.stakekit.YieldBalance import com.tangem.domain.staking.model.stakekit.YieldBalanceList import com.tangem.domain.staking.model.stakekit.action.StakingAction +import com.tangem.domain.staking.model.stakekit.action.StakingActionStatus import com.tangem.domain.staking.model.stakekit.transaction.ActionParams import com.tangem.domain.staking.model.stakekit.transaction.StakingGasEstimate import com.tangem.domain.staking.model.stakekit.transaction.StakingTransaction @@ -33,6 +35,13 @@ interface StakingRepository { fun getStakingAvailability(userWalletId: UserWalletId, cryptoCurrency: CryptoCurrency): StakingAvailability + suspend fun getActions( + userWalletId: UserWalletId, + cryptoCurrency: CryptoCurrency, + networkType: NetworkType, + stakingActionStatus: StakingActionStatus, + ): List + suspend fun fetchSingleYieldBalance( userWalletId: UserWalletId, cryptoCurrency: CryptoCurrency, diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/error/CurrencyStatusError.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/error/CurrencyStatusError.kt index c1f5dfaac8..624325cda9 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/error/CurrencyStatusError.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/error/CurrencyStatusError.kt @@ -2,7 +2,7 @@ package com.tangem.domain.tokens.error sealed class CurrencyStatusError { - object UnableToCreateCurrency : CurrencyStatusError() + data object UnableToCreateCurrency : CurrencyStatusError() data class DataError(val cause: Throwable) : CurrencyStatusError() } \ No newline at end of file diff --git a/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/repository/MockStakingRepository.kt b/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/repository/MockStakingRepository.kt index 0dc93edf8d..fa3e6119d7 100644 --- a/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/repository/MockStakingRepository.kt +++ b/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/repository/MockStakingRepository.kt @@ -117,6 +117,15 @@ class MockStakingRepository : StakingRepository { cryptoCurrency: CryptoCurrency, ): StakingAvailability = StakingAvailability.Unavailable + override suspend fun getActions( + userWalletId: UserWalletId, + cryptoCurrency: CryptoCurrency, + networkType: NetworkType, + stakingActionStatus: StakingActionStatus, + ): List { + return emptyList() + } + override suspend fun fetchSingleYieldBalance( userWalletId: UserWalletId, cryptoCurrency: CryptoCurrency, diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/StakingUiState.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/StakingUiState.kt index 56aa176637..cdc4967df2 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/StakingUiState.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/StakingUiState.kt @@ -9,7 +9,6 @@ import com.tangem.core.ui.components.list.RoundedListWithDividersItemData import com.tangem.core.ui.event.StateEvent import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.pullToRefresh.PullToRefreshConfig -import com.tangem.domain.staking.model.PendingTransaction import com.tangem.domain.staking.model.stakekit.PendingAction import com.tangem.domain.staking.model.stakekit.Yield import com.tangem.domain.staking.model.stakekit.action.StakingActionCommonType @@ -120,7 +119,6 @@ internal sealed class StakingStates { val transactionDoneState: TransactionDoneState, val isApprovalNeeded: Boolean, val reduceAmountBy: BigDecimal?, - val possiblePendingTransaction: PendingTransaction?, ) : ConfirmationState() data class Empty( diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/helpers/StakingBalanceUpdater.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/helpers/StakingBalanceUpdater.kt index 55446f2303..aa8342846e 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/helpers/StakingBalanceUpdater.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/helpers/StakingBalanceUpdater.kt @@ -1,6 +1,9 @@ package com.tangem.features.staking.impl.presentation.state.helpers import com.tangem.domain.staking.FetchStakingYieldBalanceUseCase +import com.tangem.domain.staking.FetchActionsUseCase +import com.tangem.domain.staking.model.stakekit.Yield +import com.tangem.domain.staking.model.stakekit.action.StakingActionStatus import com.tangem.domain.tokens.FetchPendingTransactionsUseCase import com.tangem.domain.tokens.UpdateDelayedNetworkStatusUseCase import com.tangem.domain.tokens.model.CryptoCurrencyStatus @@ -20,16 +23,21 @@ internal class StakingBalanceUpdater @AssistedInject constructor( private val stakingYieldBalanceUseCase: FetchStakingYieldBalanceUseCase, private val getTxHistoryItemsCountUseCase: GetTxHistoryItemsCountUseCase, private val getTxHistoryItemsUseCase: GetTxHistoryItemsUseCase, + private val fetchActionsUseCase: FetchActionsUseCase, @DelayedWork private val coroutineScope: CoroutineScope, @Assisted private val userWallet: UserWallet, @Assisted private val cryptoCurrencyStatus: CryptoCurrencyStatus, + @Assisted private val yield: Yield, ) { fun scheduleUpdates() { coroutineScope.launch { listOf( // we should update network to find pending tx after 1 sec async { - fetchPendingTransactionsUseCase(userWallet.walletId, setOf(cryptoCurrencyStatus.currency.network)) + fetchPendingTransactionsUseCase( + userWalletId = userWallet.walletId, + networks = setOf(cryptoCurrencyStatus.currency.network), + ) }, // we should update tx history and network for new balances async { @@ -41,6 +49,9 @@ internal class StakingBalanceUpdater @AssistedInject constructor( async { updateNetworkStatuses() }, + async { + updateProcessingActions() + }, ).awaitAll() } } @@ -54,6 +65,9 @@ internal class StakingBalanceUpdater @AssistedInject constructor( async { updateNetworkStatuses(delay = 0) }, + async { + updateProcessingActions() + }, ).awaitAll() } } @@ -91,9 +105,22 @@ internal class StakingBalanceUpdater @AssistedInject constructor( } } + private suspend fun updateProcessingActions() { + fetchActionsUseCase( + userWalletId = userWallet.walletId, + cryptoCurrency = cryptoCurrencyStatus.currency, + networkType = yield.token.network, + stakingActionStatus = StakingActionStatus.PROCESSING, + ) + } + @AssistedFactory interface Factory { - fun create(cryptoCurrencyStatus: CryptoCurrencyStatus, userWallet: UserWallet): StakingBalanceUpdater + fun create( + cryptoCurrencyStatus: CryptoCurrencyStatus, + userWallet: UserWallet, + yield: Yield, + ): StakingBalanceUpdater } private companion object { diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/helpers/StakingTransactionSender.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/helpers/StakingTransactionSender.kt index 2ac221975e..5e5538c4e1 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/helpers/StakingTransactionSender.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/helpers/StakingTransactionSender.kt @@ -6,7 +6,6 @@ import com.tangem.blockchain.common.transaction.Fee import com.tangem.common.ui.amountScreen.models.AmountState import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.domain.staking.* -import com.tangem.domain.staking.model.PendingTransaction import com.tangem.domain.staking.model.SubmitHashData import com.tangem.domain.staking.model.stakekit.* import com.tangem.domain.staking.model.stakekit.action.StakingActionCommonType @@ -36,7 +35,6 @@ import kotlinx.coroutines.awaitAll import kotlinx.coroutines.coroutineScope import timber.log.Timber import java.math.BigDecimal -import java.util.UUID @Suppress("LongParameterList") internal class StakingTransactionSender @AssistedInject constructor( @@ -48,7 +46,6 @@ internal class StakingTransactionSender @AssistedInject constructor( private val getExplorerTransactionUrlUseCase: GetExplorerTransactionUrlUseCase, private val submitHashUseCase: SubmitHashUseCase, private val saveUnsubmittedHashUseCase: SaveUnsubmittedHashUseCase, - private val savePendingTransactionUseCase: SavePendingTransactionUseCase, private val analyticsEventHandler: AnalyticsEventHandler, @Assisted private val cryptoCurrencyStatus: CryptoCurrencyStatus, @Assisted private val userWallet: UserWallet, @@ -57,7 +54,7 @@ internal class StakingTransactionSender @AssistedInject constructor( ) { private val balanceUpdater: StakingBalanceUpdater - get() = stakingBalanceUpdater.create(cryptoCurrencyStatus, userWallet) + get() = stakingBalanceUpdater.create(cryptoCurrencyStatus, userWallet, yield) suspend fun constructAndSendTransactions( onConstructSuccess: (List) -> Unit, @@ -95,16 +92,11 @@ internal class StakingTransactionSender @AssistedInject constructor( sendStakingTransaction( fullTransactionsData = fullTransactionsData, - possiblePendingTransaction = composePendingTransaction(confirmationState), onSendSuccess = onSendSuccess, onSendError = onSendError, ) } - private fun composePendingTransaction(confirmationState: StakingStates.ConfirmationState.Data): PendingTransaction { - return confirmationState.possiblePendingTransaction ?: composeStakeTransaction() - } - private suspend fun getStakingTransactions( state: StakingUiState, confirmationState: StakingStates.ConfirmationState.Data, @@ -214,7 +206,6 @@ internal class StakingTransactionSender @AssistedInject constructor( private suspend fun sendStakingTransaction( fullTransactionsData: List, - possiblePendingTransaction: PendingTransaction, onSendSuccess: (txUrl: String) -> Unit, onSendError: (SendTransactionError?) -> Unit, ) { @@ -230,7 +221,6 @@ internal class StakingTransactionSender @AssistedInject constructor( submitHash( transactions = fullTransactionsData.map { it.stakeKitTransaction }, transactionHashes = transactionHashes, - pendingTransaction = possiblePendingTransaction, ) val txUrl = getExplorerTransactionUrlUseCase( txHash = transactionHashes.last(), @@ -243,11 +233,7 @@ internal class StakingTransactionSender @AssistedInject constructor( ) } - private suspend fun submitHash( - transactions: List, - transactionHashes: List, - pendingTransaction: PendingTransaction, - ) { + private suspend fun submitHash(transactions: List, transactionHashes: List) { transactions .zip(transactionHashes) .forEach { (transaction, transactionHash) -> @@ -255,7 +241,6 @@ internal class StakingTransactionSender @AssistedInject constructor( SubmitHashData( transactionId = transaction.id, transactionHash = transactionHash, - pendingTransaction = pendingTransaction, ), ) .onLeft { error -> @@ -271,9 +256,6 @@ internal class StakingTransactionSender @AssistedInject constructor( ) }.onRight { Timber.d("Successful hash submission") - if (transaction.type != StakingTransactionType.FREEZE_ENERGY) { - savePendingTransactionUseCase.invoke(userWallet.walletId, pendingTransaction) - } } } } @@ -292,23 +274,6 @@ internal class StakingTransactionSender @AssistedInject constructor( ) } - private fun composeStakeTransaction(): PendingTransaction { - val state = stateController.value - - val yieldBalance = cryptoCurrencyStatus.value.yieldBalance as? YieldBalance.Data - val token = yield.getCurrentToken(cryptoCurrencyStatus.currency.id.rawCurrencyId) - - return PendingTransaction( - groupId = UUID.randomUUID().toString(), - token = token, - type = BalanceType.STAKED, - amount = (state.amountState as? AmountState.Data)?.amountTextField?.cryptoAmount?.value ?: BigDecimal.ZERO, - rawCurrencyId = cryptoCurrencyStatus.currency.id.rawCurrencyId, - validator = (state.validatorState as? StakingStates.ValidatorState.Data)?.chosenValidator, - balancesId = yieldBalance?.getBalancesUniqueId() ?: 0, - ) - } - private data class FullTransactionData( val stakeKitTransaction: StakingTransaction, val tangemTransaction: TransactionData.Compiled, diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/previewdata/ConfirmationStatePreviewData.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/previewdata/ConfirmationStatePreviewData.kt index 016a6a0625..dbcf70f6bb 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/previewdata/ConfirmationStatePreviewData.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/previewdata/ConfirmationStatePreviewData.kt @@ -47,6 +47,5 @@ internal object ConfirmationStatePreviewData { isApprovalNeeded = false, reduceAmountBy = null, pendingActions = null, - possiblePendingTransaction = null, ) } \ No newline at end of file diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetConfirmationStateInitTransformer.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetConfirmationStateInitTransformer.kt index a37a92cdcd..8387933908 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetConfirmationStateInitTransformer.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetConfirmationStateInitTransformer.kt @@ -63,7 +63,6 @@ internal class SetConfirmationStateInitTransformer( reduceAmountBy = null, pendingAction = pendingAction, pendingActions = pendingActions.takeIf { isComposePendingActions }, - possiblePendingTransaction = null, ), ) } diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetPossiblePendingTransactionTransformer.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetPossiblePendingTransactionTransformer.kt deleted file mode 100644 index 26508e49b5..0000000000 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetPossiblePendingTransactionTransformer.kt +++ /dev/null @@ -1,40 +0,0 @@ -package com.tangem.features.staking.impl.presentation.state.transformers - -import com.tangem.domain.staking.model.PendingTransaction -import com.tangem.domain.staking.model.stakekit.Yield -import com.tangem.domain.staking.model.stakekit.YieldBalance -import com.tangem.domain.tokens.model.CryptoCurrencyStatus -import com.tangem.features.staking.impl.presentation.state.BalanceState -import com.tangem.features.staking.impl.presentation.state.StakingStates -import com.tangem.features.staking.impl.presentation.state.StakingUiState -import com.tangem.utils.transformer.Transformer - -internal class SetPossiblePendingTransactionTransformer( - private val yield: Yield, - private val balanceState: BalanceState, - private val cryptoCurrencyStatus: CryptoCurrencyStatus, -) : Transformer { - - override fun transform(prevState: StakingUiState): StakingUiState { - val possibleConfirmationState = - prevState.confirmationState as? StakingStates.ConfirmationState.Data ?: return prevState - val yieldBalance = cryptoCurrencyStatus.value.yieldBalance as? YieldBalance.Data - - val balancesId = yieldBalance?.getBalancesUniqueId() ?: 0 - val token = yield.getCurrentToken(cryptoCurrencyStatus.currency.id.rawCurrencyId) - - return prevState.copy( - confirmationState = possibleConfirmationState.copy( - possiblePendingTransaction = PendingTransaction( - groupId = balanceState.groupId, - token = token, - type = balanceState.type, - amount = balanceState.cryptoDecimal, - rawCurrencyId = balanceState.rawCurrencyId, - validator = balanceState.validator, - balancesId = balancesId, - ), - ), - ) - } -} \ No newline at end of file diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/viewmodel/StakingViewModel.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/viewmodel/StakingViewModel.kt index 6639fdf7cb..6850137ee4 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/viewmodel/StakingViewModel.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/viewmodel/StakingViewModel.kt @@ -25,14 +25,13 @@ import com.tangem.domain.feedback.SaveBlockchainErrorUseCase import com.tangem.domain.feedback.SendFeedbackEmailUseCase import com.tangem.domain.feedback.models.BlockchainErrorInfo import com.tangem.domain.feedback.models.FeedbackEmailType -import com.tangem.domain.staking.InvalidatePendingTransactionsUseCase -import com.tangem.domain.staking.IsAnyTokenStakedUseCase -import com.tangem.domain.staking.IsApproveNeededUseCase +import com.tangem.domain.staking.* import com.tangem.domain.staking.model.StakingApproval import com.tangem.domain.staking.model.stakekit.BalanceItem import com.tangem.domain.staking.model.stakekit.PendingAction import com.tangem.domain.staking.model.stakekit.Yield import com.tangem.domain.staking.model.stakekit.YieldBalance +import com.tangem.domain.staking.model.stakekit.action.StakingAction import com.tangem.domain.staking.model.stakekit.action.StakingActionCommonType import com.tangem.domain.staking.model.stakekit.transaction.StakingTransaction import com.tangem.domain.tokens.* @@ -74,7 +73,6 @@ import kotlinx.collections.immutable.persistentListOf import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.* import kotlinx.coroutines.launch -import timber.log.Timber import java.math.BigDecimal import java.util.concurrent.CopyOnWriteArrayList import javax.inject.Inject @@ -108,6 +106,7 @@ internal class StakingViewModel @Inject constructor( private val stakingBalanceUpdater: StakingBalanceUpdater.Factory, private val analyticsEventHandler: AnalyticsEventHandler, private val sendFeedbackEmailUseCase: SendFeedbackEmailUseCase, + private val getActionsUseCase: GetActionsUseCase, @DelayedWork private val coroutineScope: CoroutineScope, savedStateHandle: SavedStateHandle, ) : ViewModel(), DefaultLifecycleObserver, StakingClickIntents { @@ -131,6 +130,7 @@ internal class StakingViewModel @Inject constructor( ?: error("This screen can't be opened without `Yield`") private var cryptoCurrencyStatus: CryptoCurrencyStatus by Delegates.notNull() + private var processingActions: List = emptyList() private var feeCryptoCurrencyStatus: CryptoCurrencyStatus? = null private var innerRouter: InnerStakingRouter by Delegates.notNull() @@ -141,9 +141,8 @@ internal class StakingViewModel @Inject constructor( get() { val yieldBalance = cryptoCurrencyStatus.value.yieldBalance as? YieldBalance.Data return invalidatePendingTransactionsUseCase( - userWalletId = userWallet.walletId, balanceItems = yieldBalance?.balance?.items ?: emptyList(), - balancesId = yieldBalance?.getBalancesUniqueId() ?: 0, + processingActions = processingActions, ).getOrElse { emptyList() } } @@ -153,6 +152,7 @@ internal class StakingViewModel @Inject constructor( stakingBalanceUpdater.create( cryptoCurrencyStatus, userWallet, + yield, ) } @@ -215,11 +215,6 @@ internal class StakingViewModel @Inject constructor( override fun onNextClick(balanceState: BalanceState?) { stakingStateRouter.onNextClick() - if (balanceState != null && (isInitState() || isAssentState())) { - stateController.update( - SetPossiblePendingTransactionTransformer(yield, balanceState, cryptoCurrencyStatus), - ) - } } override fun getFee() { @@ -281,7 +276,6 @@ internal class StakingViewModel @Inject constructor( transactionsInProgress.addAll(constructedTransactions) }, onConstructError = { error -> - Timber.e(error.toString()) analyticsEventHandler.send( StakingAnalyticsEvents.StakekitError( token = value.cryptoCurrencySymbol, @@ -297,7 +291,6 @@ internal class StakingViewModel @Inject constructor( stateController.update(SetConfirmationStateCompletedTransformer(txUrl)) }, onSendError = { error -> - Timber.e(error.toString()) analyticsEventHandler.send(StakingAnalyticsEvents.TransactionError(value.cryptoCurrencySymbol)) stakingEventFactory.createSendTransactionErrorAlert(error) stateController.update(SetConfirmationStateResetAssentTransformer) @@ -527,7 +520,6 @@ internal class StakingViewModel @Inject constructor( userWalletId = userWalletId, ).fold( ifLeft = { error -> - Timber.e(error.toString()) analyticsEventHandler.send(StakingAnalyticsEvents.TransactionError(value.cryptoCurrencySymbol)) stateController.update( SetConfirmationStateAssentApprovalTransformer( @@ -549,7 +541,6 @@ internal class StakingViewModel @Inject constructor( network = tokenCryptoCurrency.network, ).fold( ifLeft = { error -> - Timber.e(error.toString()) analyticsEventHandler.send(StakingAnalyticsEvents.TransactionError(value.cryptoCurrencySymbol)) stateController.update( SetConfirmationStateAssentApprovalTransformer( @@ -760,7 +751,6 @@ internal class StakingViewModel @Inject constructor( userWallet = wallet }, ifLeft = { - Timber.e(it.toString()) stakingEventFactory.createGenericErrorAlert(it.toString()) stateController.update(SetConfirmationStateResetAssentTransformer) }, @@ -790,10 +780,10 @@ internal class StakingViewModel @Inject constructor( setupApprovalNeeded() setupIsAnyTokenStaked() checkIfSubtractAvailable() + subscribeOnActionsUpdates() subscribeOnStepChanges() }, ifLeft = { error -> - Timber.e(error.toString()) stakingEventFactory.createGenericErrorAlert(error.toString()) stateController.update(SetConfirmationStateResetAssentTransformer) }, @@ -831,18 +821,7 @@ internal class StakingViewModel @Inject constructor( .onEach { when { isInitState() -> { - stateController.updateAll( - SetInitialDataStateTransformer( - clickIntents = this@StakingViewModel, - yield = yield, - isAnyTokenStaked = isAnyTokenStaked, - cryptoCurrencyStatusProvider = Provider { cryptoCurrencyStatus }, - userWalletProvider = Provider { userWallet }, - appCurrencyProvider = Provider { appCurrency }, - balancesToShowProvider = Provider { balancesToShow }, - ), - SetConfirmationStateEmptyTransformer, - ) + updateInitialData() onRefreshSwipe(isRefreshing = false) } isAssentState() -> { @@ -862,6 +841,40 @@ internal class StakingViewModel @Inject constructor( .launchIn(viewModelScope) } + private fun subscribeOnActionsUpdates() { + getActionsUseCase( + userWalletId = userWalletId, + cryptoCurrencyId = cryptoCurrencyId, + ) + .conflate() + .distinctUntilChanged() + .onEach { result -> + result.getOrNull()?.let { actions -> + processingActions = actions + if (isInitState()) { + updateInitialData() + } + } + } + .flowOn(dispatchers.main) + .launchIn(viewModelScope) + } + + private fun updateInitialData() { + stateController.updateAll( + SetInitialDataStateTransformer( + clickIntents = this@StakingViewModel, + yield = yield, + isAnyTokenStaked = isAnyTokenStaked, + cryptoCurrencyStatusProvider = Provider { cryptoCurrencyStatus }, + userWalletProvider = Provider { userWallet }, + appCurrencyProvider = Provider { appCurrency }, + balancesToShowProvider = Provider { balancesToShow }, + ), + SetConfirmationStateEmptyTransformer, + ) + } + private fun prepareForConfirmation( pendingActions: ImmutableList = persistentListOf(), pendingAction: PendingAction? = pendingActions.firstOrNull(), diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt index 1b91c07eab..a63d88e92a 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt @@ -32,7 +32,6 @@ import com.tangem.domain.common.util.cardTypesResolver import com.tangem.domain.demo.IsDemoCardUseCase import com.tangem.domain.redux.ReduxStateHolder import com.tangem.domain.settings.ShouldShowSwapPromoTokenUseCase -import com.tangem.domain.staking.GetStakingPendingTransactionsUseCase import com.tangem.domain.staking.GetStakingAvailabilityUseCase import com.tangem.domain.staking.GetStakingEntryInfoUseCase import com.tangem.domain.staking.GetStakingIntegrationIdUseCase @@ -121,7 +120,6 @@ internal class TokenDetailsViewModel @Inject constructor( private val swapTransactionStatusStore: SwapTransactionStatusStore, private val isDemoCardUseCase: IsDemoCardUseCase, private val associateAssetUseCase: AssociateAssetUseCase, - private val getStakingPendingTransactionsUseCase: GetStakingPendingTransactionsUseCase, private val reduxStateHolder: ReduxStateHolder, private val analyticsEventsHandler: AnalyticsEventHandler, private val vibratorHapticManager: VibratorHapticManager, @@ -151,8 +149,7 @@ internal class TokenDetailsViewModel @Inject constructor( private val warningsJobHolder = JobHolder() private val swapTxJobHolder = JobHolder() private val selectedAppCurrencyFlow: StateFlow = createSelectedAppCurrencyFlow() - private val stakingPendingBalances: List - get() = getStakingPendingTransactionsUseCase(userWalletId).getOrElse { emptyList() } + private val stakingPendingBalances: List = emptyList() // TODO staking private var cryptoCurrencyStatus: CryptoCurrencyStatus? = null private var stakingEntryInfo: StakingEntryInfo? = null From fea0ea58905422653ca80e986a85f5557bed1f11 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 28 Oct 2024 10:45:08 +0300 Subject: [PATCH 028/106] Updated on 2026-08-14 --- .../tap/di/domain/TokensDomainModule.kt | 3 - .../tokens/GetCryptoCurrencyActionsUseCase.kt | 4 +- .../features/markets/MarketsFeatureToggles.kt | 5 - .../markets/DefaultMarketsFeatureToggles.kt | 11 -- .../features/markets/di/FeatureModule.kt | 20 ---- .../presentation/TokenDetailsFragment.kt | 38 +++---- .../wallet/presentation/WalletFragment.kt | 24 ++-- .../router/DefaultWalletRouter.kt | 2 +- .../presentation/router/InnerWalletRouter.kt | 2 +- .../presentation/wallet/ui/WalletScreen.kt | 105 +++++------------- .../wallet/viewmodels/WalletViewModel.kt | 4 +- 11 files changed, 57 insertions(+), 161 deletions(-) delete mode 100644 features/markets/api/src/main/kotlin/com/tangem/features/markets/MarketsFeatureToggles.kt delete mode 100644 features/markets/impl/src/main/kotlin/com/tangem/features/markets/DefaultMarketsFeatureToggles.kt delete mode 100644 features/markets/impl/src/main/kotlin/com/tangem/features/markets/di/FeatureModule.kt diff --git a/app/src/main/java/com/tangem/tap/di/domain/TokensDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/TokensDomainModule.kt index 5245e5a7ec..c28ded59d7 100644 --- a/app/src/main/java/com/tangem/tap/di/domain/TokensDomainModule.kt +++ b/app/src/main/java/com/tangem/tap/di/domain/TokensDomainModule.kt @@ -7,7 +7,6 @@ import com.tangem.domain.tokens.* import com.tangem.domain.tokens.repository.* import com.tangem.domain.walletmanager.WalletManagersFacade import com.tangem.feature.swap.domain.api.SwapRepository -import com.tangem.features.markets.MarketsFeatureToggles import com.tangem.features.staking.api.featuretoggles.StakingFeatureToggles import com.tangem.utils.coroutines.CoroutineDispatcherProvider import dagger.Module @@ -234,7 +233,6 @@ internal object TokensDomainModule { networksRepository: NetworksRepository, stakingRepository: StakingRepository, stakingFeatureToggles: StakingFeatureToggles, - marketsFeatureToggles: MarketsFeatureToggles, dispatchers: CoroutineDispatcherProvider, ): GetCryptoCurrencyActionsUseCase { return GetCryptoCurrencyActionsUseCase( @@ -246,7 +244,6 @@ internal object TokensDomainModule { networksRepository = networksRepository, stakingRepository = stakingRepository, stakingFeatureToggles = stakingFeatureToggles, - marketsFeatureToggles = marketsFeatureToggles, dispatchers = dispatchers, ) } diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCryptoCurrencyActionsUseCase.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCryptoCurrencyActionsUseCase.kt index c1ac067bff..ad321616d3 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCryptoCurrencyActionsUseCase.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCryptoCurrencyActionsUseCase.kt @@ -12,7 +12,6 @@ import com.tangem.domain.tokens.repository.NetworksRepository import com.tangem.domain.tokens.repository.QuotesRepository import com.tangem.domain.walletmanager.WalletManagersFacade import com.tangem.domain.wallets.models.UserWallet -import com.tangem.features.markets.MarketsFeatureToggles import com.tangem.features.staking.api.featuretoggles.StakingFeatureToggles import com.tangem.utils.coroutines.CoroutineDispatcherProvider import com.tangem.utils.isNullOrZero @@ -34,7 +33,6 @@ class GetCryptoCurrencyActionsUseCase( private val networksRepository: NetworksRepository, private val stakingRepository: StakingRepository, private val stakingFeatureToggles: StakingFeatureToggles, - private val marketsFeatureToggles: MarketsFeatureToggles, private val dispatchers: CoroutineDispatcherProvider, ) { @@ -116,7 +114,7 @@ class GetCryptoCurrencyActionsUseCase( // markets // not a custom token - if (marketsFeatureToggles.isFeatureEnabled && cryptoCurrencyStatus.currency.id.rawCurrencyId != null) { + if (cryptoCurrencyStatus.currency.id.rawCurrencyId != null) { activeList.add(TokenActionsState.ActionState.Analytics(ScenarioUnavailabilityReason.None)) } diff --git a/features/markets/api/src/main/kotlin/com/tangem/features/markets/MarketsFeatureToggles.kt b/features/markets/api/src/main/kotlin/com/tangem/features/markets/MarketsFeatureToggles.kt deleted file mode 100644 index 34758a7b41..0000000000 --- a/features/markets/api/src/main/kotlin/com/tangem/features/markets/MarketsFeatureToggles.kt +++ /dev/null @@ -1,5 +0,0 @@ -package com.tangem.features.markets - -interface MarketsFeatureToggles { - val isFeatureEnabled: Boolean -} \ No newline at end of file diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/DefaultMarketsFeatureToggles.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/DefaultMarketsFeatureToggles.kt deleted file mode 100644 index 75062eddef..0000000000 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/DefaultMarketsFeatureToggles.kt +++ /dev/null @@ -1,11 +0,0 @@ -package com.tangem.features.markets - -import com.tangem.core.featuretoggle.manager.FeatureTogglesManager - -internal class DefaultMarketsFeatureToggles( - private val featureTogglesManager: FeatureTogglesManager, -) : MarketsFeatureToggles { - - override val isFeatureEnabled: Boolean - get() = featureTogglesManager.isFeatureEnabled("MARKETS_ENABLED") -} \ No newline at end of file diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/di/FeatureModule.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/di/FeatureModule.kt deleted file mode 100644 index a2085dfe77..0000000000 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/di/FeatureModule.kt +++ /dev/null @@ -1,20 +0,0 @@ -package com.tangem.features.markets.di - -import com.tangem.core.featuretoggle.manager.FeatureTogglesManager -import com.tangem.features.markets.DefaultMarketsFeatureToggles -import com.tangem.features.markets.MarketsFeatureToggles -import dagger.Module -import dagger.Provides -import dagger.hilt.InstallIn -import dagger.hilt.components.SingletonComponent -import javax.inject.Singleton - -@Module -@InstallIn(SingletonComponent::class) -internal object FeatureModule { - - @Provides - @Singleton - fun provideFeatureToggles(featureTogglesManager: FeatureTogglesManager): MarketsFeatureToggles = - DefaultMarketsFeatureToggles(featureTogglesManager = featureTogglesManager) -} \ No newline at end of file diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/TokenDetailsFragment.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/TokenDetailsFragment.kt index 3d8785e062..5b11ac88dd 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/TokenDetailsFragment.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/TokenDetailsFragment.kt @@ -20,7 +20,6 @@ import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.feature.tokendetails.presentation.router.InnerTokenDetailsRouter import com.tangem.feature.tokendetails.presentation.tokendetails.ui.TokenDetailsScreen import com.tangem.feature.tokendetails.presentation.tokendetails.viewmodels.TokenDetailsViewModel -import com.tangem.features.markets.MarketsFeatureToggles import com.tangem.features.markets.token.block.TokenMarketBlockComponent import com.tangem.features.tokendetails.navigation.TokenDetailsRouter import com.tangem.utils.coroutines.CoroutineDispatcherProvider @@ -36,9 +35,6 @@ internal class TokenDetailsFragment : ComposeFragment() { @Inject lateinit var tokenDetailsRouter: TokenDetailsRouter - @Inject - internal lateinit var marketsFeatureToggles: MarketsFeatureToggles - @Inject internal lateinit var coroutineDispatcherProvider: CoroutineDispatcherProvider @@ -61,27 +57,25 @@ internal class TokenDetailsFragment : ComposeFragment() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - if (marketsFeatureToggles.isFeatureEnabled) { - val cryptoCurrency: CryptoCurrency = arguments - ?.getBundle(AppRoute.CurrencyDetails.CRYPTO_CURRENCY_KEY) - ?.unbundle(CryptoCurrency.serializer()) - ?: error("Token Details screen can't be opened without `CryptoCurrency`") + val cryptoCurrency: CryptoCurrency = arguments + ?.getBundle(AppRoute.CurrencyDetails.CRYPTO_CURRENCY_KEY) + ?.unbundle(CryptoCurrency.serializer()) + ?: error("Token Details screen can't be opened without `CryptoCurrency`") - val param = cryptoCurrency.toParam() ?: return + val param = cryptoCurrency.toParam() ?: return - val appContext = DefaultAppComponentContext( - componentContext = defaultComponentContext(requireActivity().onBackPressedDispatcher), - messageHandler = uiDependencies.eventMessageHandler, - dispatchers = coroutineDispatcherProvider, - hiltComponentBuilder = componentBuilder, - replaceRouter = appRouter.asRouter(), - ) + val appContext = DefaultAppComponentContext( + componentContext = defaultComponentContext(requireActivity().onBackPressedDispatcher), + messageHandler = uiDependencies.eventMessageHandler, + dispatchers = coroutineDispatcherProvider, + hiltComponentBuilder = componentBuilder, + replaceRouter = appRouter.asRouter(), + ) - tokenMarketBlockComponent = tokenMarketBlockComponentFactory.create( - appComponentContext = appContext, - params = param, - ) - } + tokenMarketBlockComponent = tokenMarketBlockComponentFactory.create( + appComponentContext = appContext, + params = param, + ) } private fun CryptoCurrency.toParam(): TokenMarketBlockComponent.Params? { diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/WalletFragment.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/WalletFragment.kt index 74b81ff310..ba3c83a69c 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/WalletFragment.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/WalletFragment.kt @@ -12,7 +12,6 @@ import com.tangem.core.decompose.di.DecomposeComponent import com.tangem.core.ui.UiDependencies import com.tangem.core.ui.screen.ComposeFragment import com.tangem.feature.wallet.presentation.router.InnerWalletRouter -import com.tangem.features.markets.MarketsFeatureToggles import com.tangem.features.markets.entry.MarketsEntryComponent import com.tangem.features.wallet.navigation.WalletRouter import com.tangem.utils.coroutines.CoroutineDispatcherProvider @@ -43,13 +42,10 @@ internal class WalletFragment : ComposeFragment() { @Inject internal lateinit var componentBuilder: DecomposeComponent.Builder - @Inject - internal lateinit var marketsFeatureToggles: MarketsFeatureToggles - @Inject internal lateinit var appRouter: AppRouter - private var marketsEntryComponent: MarketsEntryComponent? = null + private lateinit var marketsEntryComponent: MarketsEntryComponent private val _walletRouter: InnerWalletRouter get() = requireNotNull(walletRouter as? InnerWalletRouter) { @@ -59,17 +55,15 @@ internal class WalletFragment : ComposeFragment() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - if (marketsFeatureToggles.isFeatureEnabled) { - val appContext = DefaultAppComponentContext( - componentContext = defaultComponentContext(requireActivity().onBackPressedDispatcher), - messageHandler = uiDependencies.eventMessageHandler, - dispatchers = coroutineDispatcherProvider, - hiltComponentBuilder = componentBuilder, - replaceRouter = appRouter.asRouter(), - ) + val appContext = DefaultAppComponentContext( + componentContext = defaultComponentContext(requireActivity().onBackPressedDispatcher), + messageHandler = uiDependencies.eventMessageHandler, + dispatchers = coroutineDispatcherProvider, + hiltComponentBuilder = componentBuilder, + replaceRouter = appRouter.asRouter(), + ) - marketsEntryComponent = marketsEntryComponentFactory.create(appContext) - } + marketsEntryComponent = marketsEntryComponentFactory.create(appContext) } @Composable diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/router/DefaultWalletRouter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/router/DefaultWalletRouter.kt index d08f24beca..a2e325f621 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/router/DefaultWalletRouter.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/router/DefaultWalletRouter.kt @@ -42,7 +42,7 @@ internal class DefaultWalletRouter( override fun getEntryFragment(): Fragment = WalletFragment.create() @Composable - override fun Initialize(onFinish: () -> Unit, marketsEntryComponent: MarketsEntryComponent?) { + override fun Initialize(onFinish: () -> Unit, marketsEntryComponent: MarketsEntryComponent) { this.onFinish = onFinish NavHost( diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/router/InnerWalletRouter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/router/InnerWalletRouter.kt index 40ebf62fed..344887162c 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/router/InnerWalletRouter.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/router/InnerWalletRouter.kt @@ -24,7 +24,7 @@ internal interface InnerWalletRouter : WalletRouter { * @param onFinish finish activity callback */ @Composable - fun Initialize(onFinish: () -> Unit, marketsEntryComponent: MarketsEntryComponent?) + fun Initialize(onFinish: () -> Unit, marketsEntryComponent: MarketsEntryComponent) /** Pop back stack */ fun popBackStack() diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletScreen.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletScreen.kt index 761df41a51..8dc7d7fd8f 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletScreen.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletScreen.kt @@ -45,7 +45,6 @@ import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.unit.dp import androidx.paging.compose.collectAsLazyPagingItems import com.google.accompanist.systemuicontroller.rememberSystemUiController -import com.tangem.core.ui.components.BottomFade import com.tangem.core.ui.components.atoms.Hand import com.tangem.core.ui.components.atoms.handComposableComponentHeight import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig @@ -65,7 +64,6 @@ import com.tangem.core.ui.res.LocalWindowSize import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemThemePreview import com.tangem.core.ui.test.TestTags -import com.tangem.core.ui.utils.WindowInsetsZero import com.tangem.core.ui.utils.lineTo import com.tangem.core.ui.utils.moveTo import com.tangem.core.ui.utils.toPx @@ -93,7 +91,7 @@ import kotlinx.coroutines.launch import kotlin.math.roundToInt @Composable -internal fun WalletScreen(state: WalletScreenState, marketsEntryComponent: MarketsEntryComponent?) { +internal fun WalletScreen(state: WalletScreenState, marketsEntryComponent: MarketsEntryComponent) { BackHandler(onBack = state.onBackClick) // It means that screen is still initializing @@ -137,7 +135,7 @@ private fun WalletContent( walletsListState: LazyListState, snackbarHostState: SnackbarHostState, isAutoScroll: State, - marketsEntryComponent: MarketsEntryComponent?, + marketsEntryComponent: MarketsEntryComponent, alertConfig: WalletAlertState?, onAutoScrollReset: () -> Unit, ) { @@ -239,82 +237,26 @@ private fun WalletContent( ) } - if (marketsEntryComponent != null) { - val bottomSheetState = remember { mutableStateOf(BottomSheetState.COLLAPSED) } + val bottomSheetState = remember { mutableStateOf(BottomSheetState.COLLAPSED) } - var headerSize by remember { mutableStateOf(0.dp) } + var headerSize by remember { mutableStateOf(0.dp) } - BaseScaffoldWithMarkets( - state = state, - listState = listState, - selectedWallet = selectedWallet, - snackbarHostState = snackbarHostState, - bottomSheetHeaderHeightProvider = { headerSize }, - alertConfig = alertConfig, - onBottomSheetStateChange = { bottomSheetState.value = it }, - bottomSheetContent = { - marketsEntryComponent.BottomSheetContent( - bottomSheetState = bottomSheetState, - onHeaderSizeChange = { headerSize = it }, - modifier = Modifier, - ) - }, - content = scaffoldContent, - ) - } else { - BaseScaffold( - state = state, - selectedWallet = selectedWallet, - snackbarHostState = snackbarHostState, - content = { scaffoldContent(null) }, - ) - } -} - -@OptIn(ExperimentalMaterialApi::class) -@Composable -private fun BaseScaffold( - state: WalletScreenState, - selectedWallet: WalletState, - snackbarHostState: SnackbarHostState, - content: @Composable () -> Unit, -) { - Scaffold( - topBar = { WalletTopBar(config = state.topBarConfig) }, - contentWindowInsets = WindowInsetsZero, - snackbarHost = { - WalletSnackbarHost( - snackbarHostState = snackbarHostState, - event = state.event, - modifier = Modifier.padding(bottom = TangemTheme.dimens.spacing16), + BaseScaffoldWithMarkets( + state = state, + listState = listState, + selectedWallet = selectedWallet, + snackbarHostState = snackbarHostState, + bottomSheetHeaderHeightProvider = { headerSize }, + alertConfig = alertConfig, + onBottomSheetStateChange = { bottomSheetState.value = it }, + bottomSheetContent = { + marketsEntryComponent.BottomSheetContent( + bottomSheetState = bottomSheetState, + onHeaderSizeChange = { headerSize = it }, + modifier = Modifier, ) }, - floatingActionButtonPosition = FabPosition.Center, - containerColor = TangemTheme.colors.background.secondary, - content = { - val pullRefreshState = rememberPullRefreshState( - refreshing = selectedWallet.pullToRefreshConfig.isRefreshing, - onRefresh = { - selectedWallet.pullToRefreshConfig.onRefresh(PullToRefreshConfig.ShowRefreshState()) - }, - ) - - Box( - modifier = Modifier - .pullRefresh(pullRefreshState) - .padding(it), - ) { - content() - - WalletPullToRefreshIndicator( - isRefreshing = selectedWallet.pullToRefreshConfig.isRefreshing, - state = pullRefreshState, - modifier = Modifier.align(Alignment.TopCenter), - ) - - BottomFade(Modifier.align(Alignment.BottomCenter)) - } - }, + content = scaffoldContent, ) } @@ -771,7 +713,16 @@ private fun WalletScreen_Preview(@PreviewParameter(WalletScreenPreviewProvider:: TangemThemePreview { WalletScreen( state = data, - marketsEntryComponent = null, + marketsEntryComponent = object : MarketsEntryComponent { + @Composable + override fun BottomSheetContent( + bottomSheetState: State, + onHeaderSizeChange: (Dp) -> Unit, + modifier: Modifier, + ) { + Text("Markets Content") + } + }, ) } } diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt index 14bb262aa5..fc787ace14 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt @@ -29,7 +29,6 @@ import com.tangem.feature.wallet.presentation.wallet.state.transformers.* import com.tangem.feature.wallet.presentation.wallet.state.utils.WalletEventSender import com.tangem.feature.wallet.presentation.wallet.utils.ScreenLifecycleProvider import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntents -import com.tangem.features.markets.MarketsFeatureToggles import com.tangem.features.pushnotifications.api.utils.PUSH_PERMISSION import com.tangem.features.pushnotifications.api.utils.getPushPermissionOrNull import com.tangem.utils.Provider @@ -68,7 +67,6 @@ internal class WalletViewModel @Inject constructor( private val walletNameMigrationUseCase: WalletNameMigrationUseCase, private val refreshMultiCurrencyWalletQuotesUseCase: RefreshMultiCurrencyWalletQuotesUseCase, private val shouldAskPermissionUseCase: ShouldAskPermissionUseCase, - private val marketsFeatureToggles: MarketsFeatureToggles, private val walletImageResolver: WalletImageResolver, private val tokenListStore: MultiWalletTokenListStore, analyticsEventsHandler: AnalyticsEventHandler, @@ -130,7 +128,7 @@ internal class WalletViewModel @Inject constructor( viewModelScope.launch { withContext(dispatchers.io) { delay(timeMillis = 1_800) } - if (marketsFeatureToggles.isFeatureEnabled && shouldShowMarketsTooltipUseCase()) { + if (shouldShowMarketsTooltipUseCase()) { stateHolder.update { it.copy(showMarketsOnboarding = true) } From 142ba20e3512d988607798e06309b69ba88539e6 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 28 Oct 2024 13:22:03 +0400 Subject: [PATCH 029/106] Updated on 2026-08-14 --- .../core/ui/decorations/RoundedDecorations.kt | 63 ++++++++++--------- .../DefaultSelectTokenComponent.kt | 46 ++++++++++++++ .../selecttoken/SelectTokenComponent.kt | 32 ++++++++++ .../di/SelectTokenComponentModule.kt | 18 ++++++ .../selecttoken/ui/SelectToken.kt | 45 +++++++++++++ .../tokenlist/DefaultTokenListComponent.kt | 5 +- .../tokenlist/TokenListComponent.kt | 15 +++-- .../presentation/tokenlist/ui/TokenList.kt | 11 ++-- .../tokenlist/utils/SearchTokensManager.kt | 6 +- .../multicurrency/MultiCurrencyContent.kt | 1 + .../multicurrency/MultiCurrencyContentItem.kt | 13 +--- 11 files changed, 203 insertions(+), 52 deletions(-) create mode 100644 features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/selecttoken/DefaultSelectTokenComponent.kt create mode 100644 features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/selecttoken/SelectTokenComponent.kt create mode 100644 features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/selecttoken/di/SelectTokenComponentModule.kt create mode 100644 features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/selecttoken/ui/SelectToken.kt diff --git a/core/ui/src/main/java/com/tangem/core/ui/decorations/RoundedDecorations.kt b/core/ui/src/main/java/com/tangem/core/ui/decorations/RoundedDecorations.kt index dabc6f97e2..02615d9faa 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/decorations/RoundedDecorations.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/decorations/RoundedDecorations.kt @@ -1,11 +1,13 @@ package com.tangem.core.ui.decorations +import androidx.compose.foundation.background import androidx.compose.foundation.layout.padding import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.composed import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.Dp import com.tangem.core.ui.res.TangemTheme @@ -15,46 +17,49 @@ fun Modifier.roundedShapeItemDecoration( lastIndex: Int, addDefaultPadding: Boolean = true, radius: Dp = TangemTheme.dimens.radius16, + backgroundColor: Color? = null, ): Modifier = composed { val modifier = if (addDefaultPadding) this.padding(horizontal = TangemTheme.dimens.spacing16) else this + + val applyTopPadding: @Composable Modifier.() -> Modifier = { + if (addDefaultPadding) { + padding(top = TangemTheme.dimens.spacing12) + } else { + this + } + } + + val applyShape: Modifier.(shape: RoundedCornerShape?) -> Modifier = { shape -> + if (backgroundColor != null) { + if (shape != null) { + background(color = backgroundColor, shape = shape) + } else { + background(color = backgroundColor) + } + } else { + if (shape != null) { + clip(shape = shape) + } else { + this + } + } + } + val isSingleItem = currentIndex == 0 && lastIndex == 0 when { isSingleItem -> { modifier - .then( - if (addDefaultPadding) { - Modifier.padding(top = TangemTheme.dimens.spacing12) - } else { - Modifier - }, - ) - .clip(shape = RoundedCornerShape(radius)) + .applyTopPadding() + .applyShape(RoundedCornerShape(radius)) } currentIndex == 0 -> { modifier - .then( - if (addDefaultPadding) { - Modifier.padding(top = TangemTheme.dimens.spacing12) - } else { - Modifier - }, - ) - .clip( - shape = RoundedCornerShape( - topStart = radius, - topEnd = radius, - ), - ) + .applyTopPadding() + .applyShape(RoundedCornerShape(topStart = radius, topEnd = radius)) } currentIndex == lastIndex -> { - modifier - .clip( - shape = RoundedCornerShape( - bottomStart = radius, - bottomEnd = radius, - ), - ) + modifier.applyShape(RoundedCornerShape(bottomStart = radius, bottomEnd = radius)) } - else -> modifier + else -> modifier.applyShape(null) } } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/selecttoken/DefaultSelectTokenComponent.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/selecttoken/DefaultSelectTokenComponent.kt new file mode 100644 index 0000000000..680be64c0b --- /dev/null +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/selecttoken/DefaultSelectTokenComponent.kt @@ -0,0 +1,46 @@ +package com.tangem.feature.wallet.presentation.selecttoken + +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import com.tangem.core.decompose.context.AppComponentContext +import com.tangem.core.decompose.context.child +import com.tangem.feature.wallet.presentation.selecttoken.ui.SelectToken +import com.tangem.feature.wallet.presentation.tokenlist.TokenListComponent +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject + +internal class DefaultSelectTokenComponent @AssistedInject constructor( + @Assisted appComponentContext: AppComponentContext, + tokenListComponentFactory: TokenListComponent.Factory, + @Assisted private val params: SelectTokenComponent.Params, +) : AppComponentContext by appComponentContext, SelectTokenComponent { + + private val tokenListComponent: TokenListComponent = tokenListComponentFactory.create( + context = child(key = "token_list"), + params = TokenListComponent.Params( + hasSearchBar = params.hasSearchBar, + userWalletId = params.userWalletId, + onTokenClick = params.onTokenClick, + ), + ) + + @Composable + override fun Content(modifier: Modifier) { + SelectToken( + titleResId = params.titleResId, + onBackClick = router::pop, + tokenListComponent = tokenListComponent, + modifier = modifier, + ) + } + + @AssistedFactory + interface Factory : SelectTokenComponent.Factory { + + override fun create( + context: AppComponentContext, + params: SelectTokenComponent.Params, + ): DefaultSelectTokenComponent + } +} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/selecttoken/SelectTokenComponent.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/selecttoken/SelectTokenComponent.kt new file mode 100644 index 0000000000..ff36917781 --- /dev/null +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/selecttoken/SelectTokenComponent.kt @@ -0,0 +1,32 @@ +package com.tangem.feature.wallet.presentation.selecttoken + +import androidx.annotation.StringRes +import com.tangem.core.decompose.factory.ComponentFactory +import com.tangem.core.ui.decompose.ComposableContentComponent +import com.tangem.domain.tokens.model.CryptoCurrencyStatus +import com.tangem.domain.wallets.models.UserWalletId + +/** + * Select token component + * +[REDACTED_AUTHOR] + */ +interface SelectTokenComponent : ComposableContentComponent { + + interface Factory : ComponentFactory + + /** + * Params + * + * @property hasSearchBar flag that indicates if search bar should be shown + * @property userWalletId id of multi-currency wallet + * @property titleResId resource id of title + * @property onTokenClick callback for token click + */ + data class Params( + val hasSearchBar: Boolean, + val userWalletId: UserWalletId, + @StringRes val titleResId: Int, + val onTokenClick: (CryptoCurrencyStatus) -> Unit, + ) +} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/selecttoken/di/SelectTokenComponentModule.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/selecttoken/di/SelectTokenComponentModule.kt new file mode 100644 index 0000000000..620bf54e1b --- /dev/null +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/selecttoken/di/SelectTokenComponentModule.kt @@ -0,0 +1,18 @@ +package com.tangem.feature.wallet.presentation.selecttoken.di + +import com.tangem.feature.wallet.presentation.selecttoken.DefaultSelectTokenComponent +import com.tangem.feature.wallet.presentation.selecttoken.SelectTokenComponent +import dagger.Binds +import dagger.Module +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +internal interface SelectTokenComponentModule { + + @Binds + @Singleton + fun bindSelectTokenComponentFactory(factory: DefaultSelectTokenComponent.Factory): SelectTokenComponent.Factory +} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/selecttoken/ui/SelectToken.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/selecttoken/ui/SelectToken.kt new file mode 100644 index 0000000000..56c6872198 --- /dev/null +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/selecttoken/ui/SelectToken.kt @@ -0,0 +1,45 @@ +package com.tangem.feature.wallet.presentation.selecttoken.ui + +import androidx.activity.compose.BackHandler +import androidx.annotation.StringRes +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.imePadding +import androidx.compose.foundation.layout.systemBarsPadding +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import com.tangem.core.ui.components.appbar.AppBarWithBackButton +import com.tangem.core.ui.res.TangemTheme +import com.tangem.feature.wallet.impl.R +import com.tangem.feature.wallet.presentation.tokenlist.TokenListComponent + +@Composable +internal fun SelectToken( + @StringRes titleResId: Int, + onBackClick: () -> Unit, + tokenListComponent: TokenListComponent, + modifier: Modifier = Modifier, +) { + BackHandler(onBack = onBackClick) + + Column( + modifier = modifier + .background(TangemTheme.colors.background.secondary) + .imePadding() + .systemBarsPadding(), + ) { + AppBarWithBackButton( + onBackClick = onBackClick, + text = stringResource(id = titleResId), + iconRes = R.drawable.ic_close_24, + ) + + tokenListComponent.Content( + contentPadding = PaddingValues(vertical = 8.dp, horizontal = 16.dp), + modifier = Modifier, + ) + } +} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/tokenlist/DefaultTokenListComponent.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/tokenlist/DefaultTokenListComponent.kt index c74231c846..86dfa0a67f 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/tokenlist/DefaultTokenListComponent.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/tokenlist/DefaultTokenListComponent.kt @@ -1,5 +1,6 @@ package com.tangem.feature.wallet.presentation.tokenlist +import androidx.compose.foundation.layout.PaddingValues import androidx.compose.runtime.Composable import androidx.compose.runtime.Stable import androidx.compose.runtime.getValue @@ -22,10 +23,10 @@ internal class DefaultTokenListComponent @AssistedInject constructor( private val model: TokenListModel = getOrCreateModel(params) @Composable - override fun Content(modifier: Modifier) { + override fun Content(contentPadding: PaddingValues, modifier: Modifier) { val state by model.state.collectAsStateWithLifecycle() - TokenList(state = state, modifier = modifier) + TokenList(state = state, contentPadding = contentPadding, modifier = modifier) } @AssistedFactory diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/tokenlist/TokenListComponent.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/tokenlist/TokenListComponent.kt index 3c271c0894..4bb85444e3 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/tokenlist/TokenListComponent.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/tokenlist/TokenListComponent.kt @@ -1,14 +1,19 @@ package com.tangem.feature.wallet.presentation.tokenlist +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.runtime.Composable import androidx.compose.runtime.Stable +import androidx.compose.ui.Modifier import com.tangem.core.decompose.factory.ComponentFactory -import com.tangem.core.ui.decompose.ComposableContentComponent import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.domain.wallets.models.UserWalletId /** Token list component that present list of token for multi-currency wallet */ @Stable -internal interface TokenListComponent : ComposableContentComponent { +internal interface TokenListComponent { + + @Composable + fun Content(contentPadding: PaddingValues, modifier: Modifier) /** Component factory */ interface Factory : ComponentFactory @@ -16,9 +21,9 @@ internal interface TokenListComponent : ComposableContentComponent { /** * Params * - * @property hasSearchBar flag that indicates if search bar should be shown - * @property userWalletId id of multi-currency wallet - * @property onTokenClick callback for token click + * @property hasSearchBar flag that indicates if search bar should be shown + * @property userWalletId id of multi-currency wallet + * @property onTokenClick callback for token click */ data class Params( val hasSearchBar: Boolean, diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/tokenlist/ui/TokenList.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/tokenlist/ui/TokenList.kt index e780c03e44..68a102e845 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/tokenlist/ui/TokenList.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/tokenlist/ui/TokenList.kt @@ -1,8 +1,8 @@ package com.tangem.feature.wallet.presentation.tokenlist.ui import androidx.compose.foundation.background +import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.runtime.Composable @@ -26,8 +26,8 @@ import com.tangem.feature.wallet.presentation.wallet.ui.components.multicurrency [REDACTED_AUTHOR] */ @Composable -internal fun TokenList(state: TokenListUM, modifier: Modifier = Modifier) { - LazyColumn(modifier = modifier) { +internal fun TokenList(state: TokenListUM, contentPadding: PaddingValues, modifier: Modifier = Modifier) { + LazyColumn(modifier = modifier, contentPadding = contentPadding) { itemsIndexed( items = state.items, key = { _, item -> item.id }, @@ -42,6 +42,7 @@ internal fun TokenList(state: TokenListUM, modifier: Modifier = Modifier) { currentIndex = index, lastIndex = state.items.lastIndex, addDefaultPadding = false, + backgroundColor = TangemTheme.colors.background.primary, ), ) }, @@ -55,10 +56,10 @@ private fun Preview_TokenList(@PreviewParameter(PreviewTokenListUMProvider::clas TangemThemePreview { TokenList( state = state, + contentPadding = PaddingValues(all = 16.dp), modifier = Modifier .fillMaxWidth() - .background(color = TangemTheme.colors.background.secondary) - .padding(16.dp), + .background(color = TangemTheme.colors.background.secondary), ) } } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/tokenlist/utils/SearchTokensManager.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/tokenlist/utils/SearchTokensManager.kt index 98258cca19..ff57e2818c 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/tokenlist/utils/SearchTokensManager.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/tokenlist/utils/SearchTokensManager.kt @@ -23,7 +23,11 @@ internal class SearchTokensManager @Inject constructor() { suspend fun update(value: String) { coroutineScope { - withDebounce(jobHolder) { _query.value = value } + if (value.isEmpty()) { + _query.value = value + } else { + withDebounce(jobHolder) { _query.value = value } + } } } } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/multicurrency/MultiCurrencyContent.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/multicurrency/MultiCurrencyContent.kt index e0563a4400..14c3dc4b41 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/multicurrency/MultiCurrencyContent.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/multicurrency/MultiCurrencyContent.kt @@ -64,6 +64,7 @@ private fun LazyListScope.contentItems( modifier = modifier.roundedShapeItemDecoration( currentIndex = index, lastIndex = items.lastIndex, + backgroundColor = TangemTheme.colors.background.primary, ), ) }, diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/multicurrency/MultiCurrencyContentItem.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/multicurrency/MultiCurrencyContentItem.kt index 0b8e4ea13b..7f26ab0399 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/multicurrency/MultiCurrencyContentItem.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/multicurrency/MultiCurrencyContentItem.kt @@ -1,6 +1,5 @@ package com.tangem.feature.wallet.presentation.wallet.ui.components.multicurrency -import androidx.compose.foundation.background import androidx.compose.foundation.layout.padding import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier @@ -8,7 +7,6 @@ import androidx.compose.ui.unit.dp import com.tangem.core.ui.components.fields.SearchBar import com.tangem.core.ui.components.token.TokenItem import com.tangem.core.ui.extensions.resolveReference -import com.tangem.core.ui.res.TangemTheme import com.tangem.feature.wallet.presentation.common.component.NetworkTitleItem import com.tangem.feature.wallet.presentation.wallet.state.model.WalletTokensListState.TokensListItemState @@ -26,24 +24,19 @@ internal fun MultiCurrencyContentItem( isBalanceHidden: Boolean, modifier: Modifier = Modifier, ) { - val modifierWithBackground = modifier.background(color = TangemTheme.colors.background.primary) - when (state) { is TokensListItemState.NetworkGroupTitle -> { - NetworkTitleItem(networkName = state.name.resolveReference(), modifier = modifierWithBackground) + NetworkTitleItem(networkName = state.name.resolveReference(), modifier = modifier) } is TokensListItemState.Token -> { TokenItem( state = state.state, isBalanceHidden = isBalanceHidden, - modifier = modifierWithBackground, + modifier = modifier, ) } is TokensListItemState.SearchBar -> { - SearchBar( - state = state.searchBarUM, - modifier = modifierWithBackground.padding(all = 12.dp), - ) + SearchBar(state = state.searchBarUM, modifier = modifier.padding(all = 12.dp)) } } } \ No newline at end of file From 9230cc46946f8bf2b11149c8e64c09c64daf31cb Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 28 Oct 2024 14:08:36 +0300 Subject: [PATCH 030/106] Updated on 2026-08-14 --- .../configs/feature_toggles_config.json | 4 ++ features/onboarding-legacy/.gitignore | 1 + features/onboarding-legacy/build.gradle.kts | 64 +++++++++++++++++++ .../onboarding/legacy/di/FeatureModule.kt | 18 ++++++ .../DefaultOnboardingLegacyFeatureToggles.kt | 11 ++++ .../OnboardingLegacyFeatureToggles.kt | 5 ++ settings.gradle.kts | 1 + 7 files changed, 104 insertions(+) create mode 100644 features/onboarding-legacy/.gitignore create mode 100644 features/onboarding-legacy/build.gradle.kts create mode 100644 features/onboarding-legacy/src/main/kotlin/com/tangem/feature/onboarding/legacy/di/FeatureModule.kt create mode 100644 features/onboarding-legacy/src/main/kotlin/com/tangem/feature/onboarding/legacy/featuretoggle/DefaultOnboardingLegacyFeatureToggles.kt create mode 100644 features/onboarding-legacy/src/main/kotlin/com/tangem/feature/onboarding/legacy/featuretoggle/OnboardingLegacyFeatureToggles.kt diff --git a/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json b/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json index 3c29124728..5191b7778d 100644 --- a/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json +++ b/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json @@ -30,5 +30,9 @@ { "name": "MAIN_ACTION_BUTTONS_ENABLED", "version": "undefined" + }, + { + "name": "ONBOARDING_CODE_REFACTORING_ENABLED", + "version": "undefined" } ] diff --git a/features/onboarding-legacy/.gitignore b/features/onboarding-legacy/.gitignore new file mode 100644 index 0000000000..42afabfd2a --- /dev/null +++ b/features/onboarding-legacy/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/features/onboarding-legacy/build.gradle.kts b/features/onboarding-legacy/build.gradle.kts new file mode 100644 index 0000000000..d82dd86a7d --- /dev/null +++ b/features/onboarding-legacy/build.gradle.kts @@ -0,0 +1,64 @@ +plugins { + alias(deps.plugins.android.library) + alias(deps.plugins.kotlin.android) + alias(deps.plugins.kotlin.kapt) + alias(deps.plugins.kotlin.serialization) + alias(deps.plugins.hilt.android) + id("configuration") +} + +android { + namespace = "com.tangem.feature.onboarding.legacy" +} + +dependencies { + /** Core modules */ + implementation(project(":common")) + implementation(project(":domain:models")) + implementation(project(":core:featuretoggles")) + implementation(project(":core:datasource")) + implementation(project(":core:analytics")) + implementation(projects.core.analytics.models) + implementation(project(":core:utils")) + implementation(project(":core:ui")) + implementation(project(":core:res")) + implementation(project(":data:common")) + + /** Tangem libraries */ + implementation(deps.tangem.card.core) + implementation(deps.tangem.card.android) { + exclude(module = "joda-time") + } + + /** AndroidX libraries */ + implementation(deps.androidx.core.ktx) + implementation(deps.androidx.appCompat) + implementation(deps.androidx.fragment.ktx) + implementation(deps.androidx.constraintLayout) + implementation(deps.androidx.activity.compose) + implementation(deps.lifecycle.runtime.ktx) + implementation(deps.lifecycle.common.java8) + implementation(deps.lifecycle.viewModel.ktx) + + /** Compose libraries */ + implementation(deps.compose.material) + implementation(deps.compose.material3) + implementation(deps.compose.animation) + implementation(deps.compose.foundation) + implementation(deps.compose.ui) + implementation(deps.compose.ui.tooling) + implementation(deps.compose.coil) + implementation(deps.compose.shimmer) + + implementation(deps.tangem.card.core) + + /** Other libraries */ + implementation(deps.compose.shimmer) + implementation(deps.kotlin.immutable.collections) + implementation(deps.kotlin.serialization) + implementation(deps.timber) + + /** DI */ + implementation(deps.hilt.android) + kapt(deps.hilt.kapt) +} \ No newline at end of file diff --git a/features/onboarding-legacy/src/main/kotlin/com/tangem/feature/onboarding/legacy/di/FeatureModule.kt b/features/onboarding-legacy/src/main/kotlin/com/tangem/feature/onboarding/legacy/di/FeatureModule.kt new file mode 100644 index 0000000000..58798b2583 --- /dev/null +++ b/features/onboarding-legacy/src/main/kotlin/com/tangem/feature/onboarding/legacy/di/FeatureModule.kt @@ -0,0 +1,18 @@ +package com.tangem.feature.onboarding.legacy.di + +import com.tangem.feature.onboarding.legacy.featuretoggle.DefaultOnboardingLegacyFeatureToggles +import com.tangem.feature.onboarding.legacy.featuretoggle.OnboardingLegacyFeatureToggles +import dagger.Binds +import dagger.Module +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +internal interface FeatureModule { + + @Singleton + @Binds + fun provideFeatureToggles(featureToggles: DefaultOnboardingLegacyFeatureToggles): OnboardingLegacyFeatureToggles +} \ No newline at end of file diff --git a/features/onboarding-legacy/src/main/kotlin/com/tangem/feature/onboarding/legacy/featuretoggle/DefaultOnboardingLegacyFeatureToggles.kt b/features/onboarding-legacy/src/main/kotlin/com/tangem/feature/onboarding/legacy/featuretoggle/DefaultOnboardingLegacyFeatureToggles.kt new file mode 100644 index 0000000000..d46fdf1c92 --- /dev/null +++ b/features/onboarding-legacy/src/main/kotlin/com/tangem/feature/onboarding/legacy/featuretoggle/DefaultOnboardingLegacyFeatureToggles.kt @@ -0,0 +1,11 @@ +package com.tangem.feature.onboarding.legacy.featuretoggle + +import com.tangem.core.featuretoggle.manager.FeatureTogglesManager +import javax.inject.Inject + +internal class DefaultOnboardingLegacyFeatureToggles @Inject constructor( + private val featureTogglesManager: FeatureTogglesManager, +) : OnboardingLegacyFeatureToggles { + override val isRewrittenOnboardingCodeInUse: Boolean + get() = featureTogglesManager.isFeatureEnabled("ONBOARDING_CODE_REFACTORING_ENABLED") +} \ No newline at end of file diff --git a/features/onboarding-legacy/src/main/kotlin/com/tangem/feature/onboarding/legacy/featuretoggle/OnboardingLegacyFeatureToggles.kt b/features/onboarding-legacy/src/main/kotlin/com/tangem/feature/onboarding/legacy/featuretoggle/OnboardingLegacyFeatureToggles.kt new file mode 100644 index 0000000000..aa692ec61e --- /dev/null +++ b/features/onboarding-legacy/src/main/kotlin/com/tangem/feature/onboarding/legacy/featuretoggle/OnboardingLegacyFeatureToggles.kt @@ -0,0 +1,5 @@ +package com.tangem.feature.onboarding.legacy.featuretoggle + +interface OnboardingLegacyFeatureToggles { + val isRewrittenOnboardingCodeInUse: Boolean +} \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index f3b70b2d8b..2876b342f6 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -151,6 +151,7 @@ include(":libs:visa") // region Feature modules include(":features:onboarding") +include(":features:onboarding-legacy") include(":features:referral:data") include(":features:referral:domain") From 41131e5b73af4134e5d6fa535af09e987a59beec Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 28 Oct 2024 15:36:35 +0400 Subject: [PATCH 031/106] Updated on 2026-08-14 --- .../ui/components/tokenlist/TokenListItem.kt | 23 +++++----- .../tokenlist/internal}/NetworkTitleItem.kt | 19 +++++++-- .../tokenlist/state/TokensListItemUM.kt | 42 +++++++++++++++++++ .../common/preview/WalletScreenPreviewData.kt | 17 ++++---- .../organizetokens/OrganizeTokensScreen.kt | 2 +- .../tokenlist/entity/TokenListUM.kt | 10 ++--- .../tokenlist/entity/TokenListUMController.kt | 4 +- .../UpdateTokenItemsTransformer.kt | 8 ++-- .../presentation/tokenlist/ui/TokenList.kt | 4 +- .../ui/preview/PreviewTokenListUMProvider.kt | 10 ++--- .../state/model/WalletTokensListState.kt | 29 +++---------- .../converter/TokenListStateConverter.kt | 14 +++---- .../multicurrency/MultiCurrencyContent.kt | 11 +++-- 13 files changed, 114 insertions(+), 79 deletions(-) rename features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/multicurrency/MultiCurrencyContentItem.kt => core/ui/src/main/java/com/tangem/core/ui/components/tokenlist/TokenListItem.kt (56%) rename {features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/component => core/ui/src/main/java/com/tangem/core/ui/components/tokenlist/internal}/NetworkTitleItem.kt (89%) create mode 100644 core/ui/src/main/java/com/tangem/core/ui/components/tokenlist/state/TokensListItemUM.kt diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/multicurrency/MultiCurrencyContentItem.kt b/core/ui/src/main/java/com/tangem/core/ui/components/tokenlist/TokenListItem.kt similarity index 56% rename from features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/multicurrency/MultiCurrencyContentItem.kt rename to core/ui/src/main/java/com/tangem/core/ui/components/tokenlist/TokenListItem.kt index 7f26ab0399..56b0ce8bf1 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/multicurrency/MultiCurrencyContentItem.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/tokenlist/TokenListItem.kt @@ -1,4 +1,4 @@ -package com.tangem.feature.wallet.presentation.wallet.ui.components.multicurrency +package com.tangem.core.ui.components.tokenlist import androidx.compose.foundation.layout.padding import androidx.compose.runtime.Composable @@ -6,36 +6,33 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import com.tangem.core.ui.components.fields.SearchBar import com.tangem.core.ui.components.token.TokenItem +import com.tangem.core.ui.components.tokenlist.internal.NetworkTitleItem +import com.tangem.core.ui.components.tokenlist.state.TokensListItemUM import com.tangem.core.ui.extensions.resolveReference -import com.tangem.feature.wallet.presentation.common.component.NetworkTitleItem -import com.tangem.feature.wallet.presentation.wallet.state.model.WalletTokensListState.TokensListItemState /** * Multi-currency content item * - * @param state item state - * @param modifier modifier + * @param state component UI model + * @param isBalanceHidden flag that shows/hides balance + * @param modifier modifier * [REDACTED_AUTHOR] */ @Composable -internal fun MultiCurrencyContentItem( - state: TokensListItemState, - isBalanceHidden: Boolean, - modifier: Modifier = Modifier, -) { +fun TokenListItem(state: TokensListItemUM, isBalanceHidden: Boolean, modifier: Modifier = Modifier) { when (state) { - is TokensListItemState.NetworkGroupTitle -> { + is TokensListItemUM.NetworkGroupTitle -> { NetworkTitleItem(networkName = state.name.resolveReference(), modifier = modifier) } - is TokensListItemState.Token -> { + is TokensListItemUM.Token -> { TokenItem( state = state.state, isBalanceHidden = isBalanceHidden, modifier = modifier, ) } - is TokensListItemState.SearchBar -> { + is TokensListItemUM.SearchBar -> { SearchBar(state = state.searchBarUM, modifier = modifier.padding(all = 12.dp)) } } diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/component/NetworkTitleItem.kt b/core/ui/src/main/java/com/tangem/core/ui/components/tokenlist/internal/NetworkTitleItem.kt similarity index 89% rename from features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/component/NetworkTitleItem.kt rename to core/ui/src/main/java/com/tangem/core/ui/components/tokenlist/internal/NetworkTitleItem.kt index 5bd9e5e129..4f6bf25949 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/component/NetworkTitleItem.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/tokenlist/internal/NetworkTitleItem.kt @@ -1,4 +1,4 @@ -package com.tangem.feature.wallet.presentation.common.component +package com.tangem.core.ui.components.tokenlist.internal import android.content.res.Configuration import androidx.compose.foundation.background @@ -17,21 +17,34 @@ import androidx.compose.ui.res.vectorResource import androidx.compose.ui.tooling.preview.Preview 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.rows.NetworkTitle import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemThemePreview -import com.tangem.feature.wallet.impl.R import org.burnoutcrew.reorderable.ReorderableLazyListState import org.burnoutcrew.reorderable.detectReorder import org.burnoutcrew.reorderable.rememberReorderableLazyListState +/** + * Network title item + * + * @param networkName network name + * @param modifier modifier + */ @Composable internal fun NetworkTitleItem(networkName: String, modifier: Modifier = Modifier) { BaseNetworkTitleItem(networkName = networkName, modifier = modifier) } +/** + * Draggable network title item + * + * @param networkName network name + * @param reorderableTokenListState reorderable token list state + * @param modifier modifier + */ @Composable -internal fun DraggableNetworkTitleItem( +fun DraggableNetworkTitleItem( networkName: String, reorderableTokenListState: ReorderableLazyListState, modifier: Modifier = Modifier, diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/tokenlist/state/TokensListItemUM.kt b/core/ui/src/main/java/com/tangem/core/ui/components/tokenlist/state/TokensListItemUM.kt new file mode 100644 index 0000000000..8a26f00508 --- /dev/null +++ b/core/ui/src/main/java/com/tangem/core/ui/components/tokenlist/state/TokensListItemUM.kt @@ -0,0 +1,42 @@ +package com.tangem.core.ui.components.tokenlist.state + +import androidx.compose.runtime.Immutable +import com.tangem.core.ui.components.fields.entity.SearchBarUM +import com.tangem.core.ui.components.token.state.TokenItemState +import com.tangem.core.ui.extensions.TextReference + +/** Tokens list item state */ +@Immutable +sealed interface TokensListItemUM { + + /** Unique ID */ + val id: Any + + /** + * Search bar item + * + * @property id id + * @property searchBarUM search bar UI model + */ + data class SearchBar( + override val id: Any = "search_bar", + val searchBarUM: SearchBarUM, + ) : TokensListItemUM + + /** + * Network group title + * + * @property id id + * @property name network group name + */ + data class NetworkGroupTitle(override val id: Int, val name: TextReference) : TokensListItemUM + + /** + * Token item + * + * @property state token state + */ + data class Token(val state: TokenItemState) : TokensListItemUM { + override val id: String = state.id + } +} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/preview/WalletScreenPreviewData.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/preview/WalletScreenPreviewData.kt index 3dbc17ce2f..a871134063 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/preview/WalletScreenPreviewData.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/preview/WalletScreenPreviewData.kt @@ -3,15 +3,16 @@ package com.tangem.feature.wallet.presentation.common.preview import com.tangem.core.ui.components.currency.icon.CurrencyIconState import com.tangem.core.ui.components.marketprice.PriceChangeType import com.tangem.core.ui.components.token.state.TokenItemState +import com.tangem.core.ui.components.tokenlist.state.TokensListItemUM import com.tangem.core.ui.event.consumedEvent import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.extensions.stringReference import com.tangem.core.ui.pullToRefresh.PullToRefreshConfig -import com.tangem.core.ui.utils.BigDecimalFormatter import com.tangem.domain.wallets.models.UserWalletId import com.tangem.feature.wallet.impl.R import com.tangem.feature.wallet.presentation.common.WalletPreviewData.topBarConfig import com.tangem.feature.wallet.presentation.wallet.state.model.* +import com.tangem.utils.StringsSigns.DASH_SIGN import kotlinx.collections.immutable.persistentListOf internal object WalletScreenPreviewData { @@ -32,16 +33,16 @@ internal object WalletScreenPreviewData { private val textContentTokensState = WalletTokensListState.ContentState.Content( items = persistentListOf( - WalletTokensListState.TokensListItemState.NetworkGroupTitle( + TokensListItemUM.NetworkGroupTitle( id = 1, name = stringReference("Bitcoin"), ), - WalletTokensListState.TokensListItemState.Token(state = tokenItemState), - WalletTokensListState.TokensListItemState.NetworkGroupTitle( + TokensListItemUM.Token(state = tokenItemState), + TokensListItemUM.NetworkGroupTitle( id = 2, name = stringReference("Ethereum"), ), - WalletTokensListState.TokensListItemState.Token( + TokensListItemUM.Token( state = tokenItemState.copy( id = "2", titleState = TokenItemState.TitleState.Content(text = "Ethereum"), @@ -54,7 +55,7 @@ internal object WalletScreenPreviewData { ), ), ), - WalletTokensListState.TokensListItemState.Token( + TokensListItemUM.Token( state = TokenItemState.Unreachable( id = "3", iconState = CurrencyIconState.Locked, @@ -63,7 +64,7 @@ internal object WalletScreenPreviewData { onItemLongClick = {}, ), ), - WalletTokensListState.TokensListItemState.Token( + TokensListItemUM.Token( state = tokenItemState.copy( id = "4", titleState = TokenItemState.TitleState.Content(text = "Shiba Inu"), @@ -106,7 +107,7 @@ internal object WalletScreenPreviewData { ), imageResId = R.drawable.ill_wallet2_cards3_120_106, cardCount = 3, - balance = BigDecimalFormatter.EMPTY_BALANCE_SIGN, + balance = DASH_SIGN, onRenameClick = { _ -> }, onDeleteClick = {}, ) diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/organizetokens/OrganizeTokensScreen.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/organizetokens/OrganizeTokensScreen.kt index 17dca9d4fe..15a4f2250e 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/organizetokens/OrganizeTokensScreen.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/organizetokens/OrganizeTokensScreen.kt @@ -32,6 +32,7 @@ import com.tangem.core.ui.components.SecondaryButton import com.tangem.core.ui.components.buttons.actions.ActionButtonConfig import com.tangem.core.ui.components.buttons.actions.RoundedActionButton import com.tangem.core.ui.components.token.TokenItem +import com.tangem.core.ui.components.tokenlist.internal.DraggableNetworkTitleItem import com.tangem.core.ui.event.EventEffect import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.reordarable.ReorderableItem @@ -40,7 +41,6 @@ import com.tangem.core.ui.res.TangemThemePreview import com.tangem.core.ui.utils.WindowInsetsZero import com.tangem.feature.wallet.impl.R import com.tangem.feature.wallet.presentation.common.WalletPreviewData -import com.tangem.feature.wallet.presentation.common.component.DraggableNetworkTitleItem import com.tangem.feature.wallet.presentation.organizetokens.model.DraggableItem import com.tangem.feature.wallet.presentation.organizetokens.model.OrganizeTokensListState import com.tangem.feature.wallet.presentation.organizetokens.model.OrganizeTokensState diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/tokenlist/entity/TokenListUM.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/tokenlist/entity/TokenListUM.kt index 83d5fc1640..c86dc9a2f9 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/tokenlist/entity/TokenListUM.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/tokenlist/entity/TokenListUM.kt @@ -1,6 +1,6 @@ package com.tangem.feature.wallet.presentation.tokenlist.entity -import com.tangem.feature.wallet.presentation.wallet.state.model.WalletTokensListState.TokensListItemState +import com.tangem.core.ui.components.tokenlist.state.TokensListItemUM import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf @@ -13,17 +13,17 @@ import kotlinx.collections.immutable.persistentListOf [REDACTED_AUTHOR] */ internal data class TokenListUM( - val items: ImmutableList, + val items: ImmutableList, val isBalanceHidden: Boolean, ) { /** Get search bar if it exists */ - fun getSearchBar(): TokensListItemState.SearchBar? { - return items.firstOrNull() as? TokensListItemState.SearchBar + fun getSearchBar(): TokensListItemUM.SearchBar? { + return items.firstOrNull() as? TokensListItemUM.SearchBar } /** Get tokens */ - fun getTokens(): ImmutableList { + fun getTokens(): ImmutableList { if (getSearchBar() == null) return items return if (items.size > 1) { diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/tokenlist/entity/TokenListUMController.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/tokenlist/entity/TokenListUMController.kt index 04592b5ed0..43c5bb75b8 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/tokenlist/entity/TokenListUMController.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/tokenlist/entity/TokenListUMController.kt @@ -1,6 +1,6 @@ package com.tangem.feature.wallet.presentation.tokenlist.entity -import com.tangem.feature.wallet.presentation.wallet.state.model.WalletTokensListState.TokensListItemState +import com.tangem.core.ui.components.tokenlist.state.TokensListItemUM import kotlinx.collections.immutable.persistentListOf import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow @@ -35,7 +35,7 @@ internal class TokenListUMController @Inject constructor() { } /** Get search bar if it exists */ - fun getSearchBar(): TokensListItemState.SearchBar? { + fun getSearchBar(): TokensListItemUM.SearchBar? { return _state.value.getSearchBar() } } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/tokenlist/entity/transformer/UpdateTokenItemsTransformer.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/tokenlist/entity/transformer/UpdateTokenItemsTransformer.kt index ed4dcf5c9e..0fe02a9f42 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/tokenlist/entity/transformer/UpdateTokenItemsTransformer.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/tokenlist/entity/transformer/UpdateTokenItemsTransformer.kt @@ -2,12 +2,12 @@ package com.tangem.feature.wallet.presentation.tokenlist.entity.transformer import com.tangem.common.ui.tokens.TokenItemStateConverter import com.tangem.core.ui.components.fields.entity.SearchBarUM +import com.tangem.core.ui.components.tokenlist.state.TokensListItemUM import com.tangem.core.ui.extensions.resourceReference import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.feature.wallet.impl.R import com.tangem.feature.wallet.presentation.tokenlist.entity.TokenListUM import com.tangem.feature.wallet.presentation.tokenlist.entity.TokenListUMTransformer -import com.tangem.feature.wallet.presentation.wallet.state.model.WalletTokensListState.TokensListItemState import kotlinx.collections.immutable.toImmutableList internal class UpdateTokenItemsTransformer( @@ -20,7 +20,7 @@ internal class UpdateTokenItemsTransformer( ) : TokenListUMTransformer { override fun transform(prevState: TokenListUM): TokenListUM { - val items = tokenItemStateConverter.convertList(input = statuses).map(TokensListItemState::Token) + val items = tokenItemStateConverter.convertList(input = statuses).map(TokensListItemUM::Token) val searchBarItem = if (hasSearchBar) { prevState.getSearchBar() ?: createSearchBarItem() @@ -34,8 +34,8 @@ internal class UpdateTokenItemsTransformer( ) } - private fun createSearchBarItem(): TokensListItemState.SearchBar { - return TokensListItemState.SearchBar( + private fun createSearchBarItem(): TokensListItemUM.SearchBar { + return TokensListItemUM.SearchBar( searchBarUM = SearchBarUM( placeholderText = resourceReference(id = R.string.common_search), query = "", diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/tokenlist/ui/TokenList.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/tokenlist/ui/TokenList.kt index 68a102e845..b8fcb55c6d 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/tokenlist/ui/TokenList.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/tokenlist/ui/TokenList.kt @@ -10,12 +10,12 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.unit.dp +import com.tangem.core.ui.components.tokenlist.TokenListItem import com.tangem.core.ui.decorations.roundedShapeItemDecoration import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemThemePreview import com.tangem.feature.wallet.presentation.tokenlist.entity.TokenListUM import com.tangem.feature.wallet.presentation.tokenlist.ui.preview.PreviewTokenListUMProvider -import com.tangem.feature.wallet.presentation.wallet.ui.components.multicurrency.MultiCurrencyContentItem /** * Token list @@ -33,7 +33,7 @@ internal fun TokenList(state: TokenListUM, contentPadding: PaddingValues, modifi key = { _, item -> item.id }, contentType = { _, item -> item::class.java }, itemContent = { index, item -> - MultiCurrencyContentItem( + TokenListItem( state = item, isBalanceHidden = state.isBalanceHidden, modifier = Modifier diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/tokenlist/ui/preview/PreviewTokenListUMProvider.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/tokenlist/ui/preview/PreviewTokenListUMProvider.kt index 4a0b5ad769..1b735ea3c8 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/tokenlist/ui/preview/PreviewTokenListUMProvider.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/tokenlist/ui/preview/PreviewTokenListUMProvider.kt @@ -5,10 +5,10 @@ import com.tangem.core.ui.components.currency.icon.CurrencyIconState import com.tangem.core.ui.components.fields.entity.SearchBarUM import com.tangem.core.ui.components.marketprice.PriceChangeType import com.tangem.core.ui.components.token.state.TokenItemState +import com.tangem.core.ui.components.tokenlist.state.TokensListItemUM import com.tangem.core.ui.extensions.resourceReference import com.tangem.feature.wallet.impl.R import com.tangem.feature.wallet.presentation.tokenlist.entity.TokenListUM -import com.tangem.feature.wallet.presentation.wallet.state.model.WalletTokensListState.TokensListItemState import kotlinx.collections.immutable.toImmutableList internal class PreviewTokenListUMProvider : PreviewParameterProvider { @@ -18,11 +18,11 @@ internal class PreviewTokenListUMProvider : PreviewParameterProvider + abstract val items: ImmutableList abstract val organizeTokensButtonConfig: OrganizeTokensButtonConfig? data object Loading : ContentState() { - override val items = persistentListOf() + override val items = persistentListOf() override val organizeTokensButtonConfig = null } data class Content( - override val items: ImmutableList, + override val items: ImmutableList, override val organizeTokensButtonConfig: OrganizeTokensButtonConfig?, ) : ContentState() data object Locked : ContentState() { override val items = persistentListOf( - TokensListItemState.NetworkGroupTitle(id = 42, name = TextReference.Res(id = R.string.main_tokens)), - TokensListItemState.Token(state = TokenItemState.Locked(id = "Locked#1")), + TokensListItemUM.NetworkGroupTitle(id = 42, name = TextReference.Res(id = R.string.main_tokens)), + TokensListItemUM.Token(state = TokenItemState.Locked(id = "Locked#1")), ) override val organizeTokensButtonConfig = null } } data class OrganizeTokensButtonConfig(val isEnabled: Boolean, val onClick: () -> Unit) - - @Immutable - sealed class TokensListItemState { - - abstract val id: Any - - data class SearchBar( - override val id: Any = "search_bar", - val searchBarUM: SearchBarUM, - ) : TokensListItemState() - - data class NetworkGroupTitle(override val id: Int, val name: TextReference) : TokensListItemState() - - data class Token(val state: TokenItemState) : TokensListItemState() { - override val id: String = state.id - } - } } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/TokenListStateConverter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/TokenListStateConverter.kt index 4ae09b322f..668118d2fb 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/TokenListStateConverter.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/TokenListStateConverter.kt @@ -1,6 +1,7 @@ package com.tangem.feature.wallet.presentation.wallet.state.transformers.converter import com.tangem.common.ui.tokens.TokenItemStateConverter +import com.tangem.core.ui.components.tokenlist.state.TokensListItemUM import com.tangem.core.ui.extensions.stringReference import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.common.util.cardTypesResolver @@ -10,7 +11,6 @@ import com.tangem.domain.tokens.model.TokenList import com.tangem.domain.tokens.model.TotalFiatBalance import com.tangem.domain.wallets.models.UserWallet import com.tangem.feature.wallet.presentation.wallet.state.model.WalletTokensListState -import com.tangem.feature.wallet.presentation.wallet.state.model.WalletTokensListState.TokensListItemState import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntents import com.tangem.utils.converter.Converter import kotlinx.collections.immutable.PersistentList @@ -47,20 +47,20 @@ internal class TokenListStateConverter( } } - private fun TokenList.GroupedByNetwork.toGroupedItems(): PersistentList { + private fun TokenList.GroupedByNetwork.toGroupedItems(): PersistentList { return groups.fold(initial = persistentListOf()) { acc, group -> acc.mutate { it.addGroup(group) } } } - private fun TokenList.Ungrouped.toUngroupedItems(): PersistentList { + private fun TokenList.Ungrouped.toUngroupedItems(): PersistentList { return currencies.fold(initial = persistentListOf()) { acc, token -> acc.mutate { it.addToken(token) } } } - private fun MutableList.addGroup(group: NetworkGroup): List { - val groupTitle = TokensListItemState.NetworkGroupTitle( + private fun MutableList.addGroup(group: NetworkGroup): List { + val groupTitle = TokensListItemUM.NetworkGroupTitle( id = group.network.hashCode(), name = stringReference(group.network.name), ) @@ -71,9 +71,9 @@ internal class TokenListStateConverter( return this } - private fun MutableList.addToken(token: CryptoCurrencyStatus): List { + private fun MutableList.addToken(token: CryptoCurrencyStatus): List { val tokenItemState = tokenStatusConverter.convert(token) - add(TokensListItemState.Token(tokenItemState)) + add(TokensListItemUM.Token(tokenItemState)) return this } diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/multicurrency/MultiCurrencyContent.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/multicurrency/MultiCurrencyContent.kt index 14c3dc4b41..49c11b5799 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/multicurrency/MultiCurrencyContent.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/multicurrency/MultiCurrencyContent.kt @@ -1,6 +1,5 @@ package com.tangem.feature.wallet.presentation.wallet.ui.components.multicurrency -import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.padding @@ -14,11 +13,12 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign +import com.tangem.core.ui.components.tokenlist.TokenListItem +import com.tangem.core.ui.components.tokenlist.state.TokensListItemUM import com.tangem.core.ui.decorations.roundedShapeItemDecoration import com.tangem.core.ui.res.TangemTheme import com.tangem.feature.wallet.impl.R import com.tangem.feature.wallet.presentation.wallet.state.model.WalletTokensListState -import com.tangem.feature.wallet.presentation.wallet.state.model.WalletTokensListState.TokensListItemState import kotlinx.collections.immutable.ImmutableList private const val NON_CONTENT_TOKENS_LIST_KEY = "NON_CONTENT_TOKENS_LIST" @@ -49,7 +49,7 @@ internal fun LazyListScope.tokensListItems( } private fun LazyListScope.contentItems( - items: ImmutableList, + items: ImmutableList, modifier: Modifier = Modifier, isBalanceHidden: Boolean, ) { @@ -58,7 +58,7 @@ private fun LazyListScope.contentItems( key = { _, item -> item.id }, contentType = { _, item -> item::class.java }, itemContent = { index, item -> - MultiCurrencyContentItem( + TokenListItem( state = item, isBalanceHidden = isBalanceHidden, modifier = modifier.roundedShapeItemDecoration( @@ -71,7 +71,6 @@ private fun LazyListScope.contentItems( ) } -@OptIn(ExperimentalFoundationApi::class) private fun LazyListScope.nonContentItem(modifier: Modifier = Modifier) { item( key = NON_CONTENT_TOKENS_LIST_KEY, @@ -79,7 +78,7 @@ private fun LazyListScope.nonContentItem(modifier: Modifier = Modifier) { ) { Column( modifier = modifier - .animateItemPlacement() + .animateItem() .padding(top = TangemTheme.dimens.spacing96), verticalArrangement = Arrangement.spacedBy(space = TangemTheme.dimens.spacing16), horizontalAlignment = Alignment.CenterHorizontally, From 6ba6f5b34830585bf47f03301955e579edb55fdb Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 28 Oct 2024 16:52:58 +0400 Subject: [PATCH 032/106] Updated on 2026-08-14 --- .../kotlin/com/tangem/domain/core/lce/LceFlow.kt | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/domain/core/src/main/kotlin/com/tangem/domain/core/lce/LceFlow.kt b/domain/core/src/main/kotlin/com/tangem/domain/core/lce/LceFlow.kt index 037775753e..65e29bca2b 100644 --- a/domain/core/src/main/kotlin/com/tangem/domain/core/lce/LceFlow.kt +++ b/domain/core/src/main/kotlin/com/tangem/domain/core/lce/LceFlow.kt @@ -6,7 +6,6 @@ import com.tangem.domain.core.utils.lceContent import com.tangem.domain.core.utils.lceError import com.tangem.domain.core.utils.lceLoading import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.channels.ProducerScope import kotlinx.coroutines.channels.trySendBlocking import kotlinx.coroutines.flow.Flow @@ -38,13 +37,9 @@ class LceFlowScope @PublishedApi internal constructor( val isLoading: AtomicBoolean = AtomicBoolean(value = true) /** - * Sends a error of type [E] within the [ProducerScope] and then closes it for send. - * All subsequent sends will be ignored. + * Sends an error of type [E] within the [ProducerScope] without closing. * - * This method blocks the coroutine until a error is handled by the receiver. - * - * If the [ProducerScope] is already closed for send (e.g. after rising another error), it just raises [r] - * without closing. + * This method blocks the coroutine until an error is handled by the receiver. * * @param r Error to raise. */ @@ -52,7 +47,6 @@ class LceFlowScope @PublishedApi internal constructor( isLoading.set(false) producerScope.trySendBlocking(r.lceError()) - producerScope.close() raise.raise(r.lceError()) } @@ -88,14 +82,9 @@ class LceFlowScope @PublishedApi internal constructor( * * This method suspends until the [Lce] instance is handled by the receiver. * - * If the [ProducerScope] is closed for send (e.g. after rising a error), it does nothing. - * * @param value The [Lce] instance to send. */ - @OptIn(DelicateCoroutinesApi::class) suspend fun send(value: Lce) { - if (producerScope.isClosedForSend) return - isLoading.set(value.isLoading()) producerScope.send(value) From 5e8273c248a2d53a94db73cbe705b2ec2e85ba3c Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 28 Oct 2024 16:54:23 +0400 Subject: [PATCH 033/106] Updated on 2026-08-14 --- .../main/kotlin/com/tangem/domain/tokens/model/TokenList.kt | 2 +- .../tokens/operations/CurrenciesStatusesLceOperations.kt | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/model/TokenList.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/model/TokenList.kt index f5eaf0b0e3..2bd9431825 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/model/TokenList.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/model/TokenList.kt @@ -42,7 +42,7 @@ sealed class TokenList { ) : TokenList() /** Represents a state where the token list is empty. */ - object Empty : TokenList() { + data object Empty : TokenList() { override val totalFiatBalance: TotalFiatBalance = TotalFiatBalance.Loaded( amount = BigDecimal.ZERO, diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/CurrenciesStatusesLceOperations.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/CurrenciesStatusesLceOperations.kt index cc96309f1f..f56873489c 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/CurrenciesStatusesLceOperations.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/CurrenciesStatusesLceOperations.kt @@ -75,10 +75,9 @@ internal class CurrenciesStatusesLceOperations( ::createCurrenciesStatuses, ) .distinctUntilChanged() - .mapLatest { maybeCurrenciesStatuses -> + .collectLatest { maybeCurrenciesStatuses -> send(maybeCurrenciesStatuses) } - .launchIn(scope = this) } } From f5eca5ab2a82c309638fd6362f2dfeee840e2c62 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 28 Oct 2024 16:52:05 +0400 Subject: [PATCH 034/106] Updated on 2026-08-14 --- features/onramp/impl/build.gradle.kts | 15 ++++++ .../DefaultOnrampSelectTokenComponent.kt | 46 +++++++++++++++++++ .../OnrampSelectTokenComponent.kt} | 6 +-- .../di/OnrampSelectTokenComponentModule.kt | 20 ++++++++ .../selecttoken/ui/OnrampSelectToken.kt} | 12 ++--- .../DefaultOnrampTokenListComponent.kt} | 21 +++++---- .../tokenlist/OnrampTokenListComponent.kt} | 6 +-- .../di/OnrampTokenListComponentModule.kt | 20 ++++++++ .../di/OnrampTokenListModelModule.kt} | 10 ++-- .../onramp}/tokenlist/entity/TokenListUM.kt | 2 +- .../tokenlist/entity/TokenListUMController.kt | 2 +- .../entity/TokenListUMTransformer.kt | 2 +- .../transformer/SearchBarUMTransformer.kt | 6 +-- .../UpdateSearchBarActiveStateTransformer.kt | 4 +- .../UpdateSearchQueryTransformer.kt | 2 +- .../UpdateTokenItemsTransformer.kt | 8 ++-- .../tokenlist/model/OnrampTokenListModel.kt} | 20 ++++---- .../onramp/tokenlist/ui/OnrampTokenList.kt} | 6 +-- .../ui/preview/PreviewTokenListUMProvider.kt | 6 +-- .../tokenlist/utils/SearchTokensManager.kt | 2 +- .../DefaultSelectTokenComponent.kt | 46 ------------------- .../di/SelectTokenComponentModule.kt | 18 -------- .../tokenlist/di/TokenListComponentModule.kt | 18 -------- 23 files changed, 160 insertions(+), 138 deletions(-) create mode 100644 features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selecttoken/DefaultOnrampSelectTokenComponent.kt rename features/{wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/selecttoken/SelectTokenComponent.kt => onramp/impl/src/main/kotlin/com/tangem/features/onramp/selecttoken/OnrampSelectTokenComponent.kt} (80%) create mode 100644 features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selecttoken/di/OnrampSelectTokenComponentModule.kt rename features/{wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/selecttoken/ui/SelectToken.kt => onramp/impl/src/main/kotlin/com/tangem/features/onramp/selecttoken/ui/OnrampSelectToken.kt} (81%) rename features/{wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/tokenlist/DefaultTokenListComponent.kt => onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/DefaultOnrampTokenListComponent.kt} (55%) rename features/{wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/tokenlist/TokenListComponent.kt => onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/OnrampTokenListComponent.kt} (85%) create mode 100644 features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/di/OnrampTokenListComponentModule.kt rename features/{wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/tokenlist/di/TokenListModelModule.kt => onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/di/OnrampTokenListModelModule.kt} (53%) rename features/{wallet/impl/src/main/java/com/tangem/feature/wallet/presentation => onramp/impl/src/main/kotlin/com/tangem/features/onramp}/tokenlist/entity/TokenListUM.kt (93%) rename features/{wallet/impl/src/main/java/com/tangem/feature/wallet/presentation => onramp/impl/src/main/kotlin/com/tangem/features/onramp}/tokenlist/entity/TokenListUMController.kt (94%) rename features/{wallet/impl/src/main/java/com/tangem/feature/wallet/presentation => onramp/impl/src/main/kotlin/com/tangem/features/onramp}/tokenlist/entity/TokenListUMTransformer.kt (73%) rename features/{wallet/impl/src/main/java/com/tangem/feature/wallet/presentation => onramp/impl/src/main/kotlin/com/tangem/features/onramp}/tokenlist/entity/transformer/SearchBarUMTransformer.kt (78%) rename features/{wallet/impl/src/main/java/com/tangem/feature/wallet/presentation => onramp/impl/src/main/kotlin/com/tangem/features/onramp}/tokenlist/entity/transformer/UpdateSearchBarActiveStateTransformer.kt (84%) rename features/{wallet/impl/src/main/java/com/tangem/feature/wallet/presentation => onramp/impl/src/main/kotlin/com/tangem/features/onramp}/tokenlist/entity/transformer/UpdateSearchQueryTransformer.kt (79%) rename features/{wallet/impl/src/main/java/com/tangem/feature/wallet/presentation => onramp/impl/src/main/kotlin/com/tangem/features/onramp}/tokenlist/entity/transformer/UpdateTokenItemsTransformer.kt (85%) rename features/{wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/tokenlist/model/TokenListModel.kt => onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/model/OnrampTokenListModel.kt} (82%) rename features/{wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/tokenlist/ui/TokenList.kt => onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/ui/OnrampTokenList.kt} (90%) rename features/{wallet/impl/src/main/java/com/tangem/feature/wallet/presentation => onramp/impl/src/main/kotlin/com/tangem/features/onramp}/tokenlist/ui/preview/PreviewTokenListUMProvider.kt (93%) rename features/{wallet/impl/src/main/java/com/tangem/feature/wallet/presentation => onramp/impl/src/main/kotlin/com/tangem/features/onramp}/tokenlist/utils/SearchTokensManager.kt (92%) delete mode 100644 features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/selecttoken/DefaultSelectTokenComponent.kt delete mode 100644 features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/selecttoken/di/SelectTokenComponentModule.kt delete mode 100644 features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/tokenlist/di/TokenListComponentModule.kt diff --git a/features/onramp/impl/build.gradle.kts b/features/onramp/impl/build.gradle.kts index 94b9c2785a..8de571b7a7 100644 --- a/features/onramp/impl/build.gradle.kts +++ b/features/onramp/impl/build.gradle.kts @@ -20,8 +20,21 @@ dependencies { implementation(projects.core.ui) implementation(projects.core.featuretoggles) + /** Project - Common */ + implementation(projects.common.ui) + + /** Project - Domain */ + implementation(projects.domain.appCurrency) + implementation(projects.domain.appCurrency.models) + implementation(projects.domain.balanceHiding) + implementation(projects.domain.balanceHiding.models) + implementation(projects.domain.tokens) + implementation(projects.domain.tokens.models) + implementation(projects.domain.wallets.models) + /** DI */ implementation(deps.hilt.android) + implementation(project(":common:ui")) kapt(deps.hilt.kapt) /** AndroidX */ @@ -40,4 +53,6 @@ dependencies { /** Other */ implementation(deps.decompose.ext.compose) + implementation(deps.kotlin.immutable.collections) + implementation(deps.timber) } \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selecttoken/DefaultOnrampSelectTokenComponent.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selecttoken/DefaultOnrampSelectTokenComponent.kt new file mode 100644 index 0000000000..50ec027209 --- /dev/null +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selecttoken/DefaultOnrampSelectTokenComponent.kt @@ -0,0 +1,46 @@ +package com.tangem.features.onramp.selecttoken + +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import com.tangem.core.decompose.context.AppComponentContext +import com.tangem.core.decompose.context.child +import com.tangem.features.onramp.selecttoken.ui.OnrampSelectToken +import com.tangem.features.onramp.tokenlist.OnrampTokenListComponent +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject + +internal class DefaultOnrampSelectTokenComponent @AssistedInject constructor( + @Assisted appComponentContext: AppComponentContext, + onrampTokenListComponentFactory: OnrampTokenListComponent.Factory, + @Assisted private val params: OnrampSelectTokenComponent.Params, +) : AppComponentContext by appComponentContext, OnrampSelectTokenComponent { + + private val onrampTokenListComponent: OnrampTokenListComponent = onrampTokenListComponentFactory.create( + context = child(key = "token_list"), + params = OnrampTokenListComponent.Params( + hasSearchBar = params.hasSearchBar, + userWalletId = params.userWalletId, + onTokenClick = params.onTokenClick, + ), + ) + + @Composable + override fun Content(modifier: Modifier) { + OnrampSelectToken( + titleResId = params.titleResId, + onBackClick = router::pop, + onrampTokenListComponent = onrampTokenListComponent, + modifier = modifier, + ) + } + + @AssistedFactory + interface Factory : OnrampSelectTokenComponent.Factory { + + override fun create( + context: AppComponentContext, + params: OnrampSelectTokenComponent.Params, + ): DefaultOnrampSelectTokenComponent + } +} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/selecttoken/SelectTokenComponent.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selecttoken/OnrampSelectTokenComponent.kt similarity index 80% rename from features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/selecttoken/SelectTokenComponent.kt rename to features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selecttoken/OnrampSelectTokenComponent.kt index ff36917781..69aa520462 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/selecttoken/SelectTokenComponent.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selecttoken/OnrampSelectTokenComponent.kt @@ -1,4 +1,4 @@ -package com.tangem.feature.wallet.presentation.selecttoken +package com.tangem.features.onramp.selecttoken import androidx.annotation.StringRes import com.tangem.core.decompose.factory.ComponentFactory @@ -11,9 +11,9 @@ import com.tangem.domain.wallets.models.UserWalletId * [REDACTED_AUTHOR] */ -interface SelectTokenComponent : ComposableContentComponent { +interface OnrampSelectTokenComponent : ComposableContentComponent { - interface Factory : ComponentFactory + interface Factory : ComponentFactory /** * Params diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selecttoken/di/OnrampSelectTokenComponentModule.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selecttoken/di/OnrampSelectTokenComponentModule.kt new file mode 100644 index 0000000000..01bece010d --- /dev/null +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selecttoken/di/OnrampSelectTokenComponentModule.kt @@ -0,0 +1,20 @@ +package com.tangem.features.onramp.selecttoken.di + +import com.tangem.features.onramp.selecttoken.DefaultOnrampSelectTokenComponent +import com.tangem.features.onramp.selecttoken.OnrampSelectTokenComponent +import dagger.Binds +import dagger.Module +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +internal interface OnrampSelectTokenComponentModule { + + @Binds + @Singleton + fun bindOnrampSelectTokenComponentFactory( + factory: DefaultOnrampSelectTokenComponent.Factory, + ): OnrampSelectTokenComponent.Factory +} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/selecttoken/ui/SelectToken.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selecttoken/ui/OnrampSelectToken.kt similarity index 81% rename from features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/selecttoken/ui/SelectToken.kt rename to features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selecttoken/ui/OnrampSelectToken.kt index 56c6872198..653883fb90 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/selecttoken/ui/SelectToken.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selecttoken/ui/OnrampSelectToken.kt @@ -1,4 +1,4 @@ -package com.tangem.feature.wallet.presentation.selecttoken.ui +package com.tangem.features.onramp.selecttoken.ui import androidx.activity.compose.BackHandler import androidx.annotation.StringRes @@ -13,14 +13,14 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.tangem.core.ui.components.appbar.AppBarWithBackButton import com.tangem.core.ui.res.TangemTheme -import com.tangem.feature.wallet.impl.R -import com.tangem.feature.wallet.presentation.tokenlist.TokenListComponent +import com.tangem.features.onramp.impl.R +import com.tangem.features.onramp.tokenlist.OnrampTokenListComponent @Composable -internal fun SelectToken( +internal fun OnrampSelectToken( @StringRes titleResId: Int, onBackClick: () -> Unit, - tokenListComponent: TokenListComponent, + onrampTokenListComponent: OnrampTokenListComponent, modifier: Modifier = Modifier, ) { BackHandler(onBack = onBackClick) @@ -37,7 +37,7 @@ internal fun SelectToken( iconRes = R.drawable.ic_close_24, ) - tokenListComponent.Content( + onrampTokenListComponent.Content( contentPadding = PaddingValues(vertical = 8.dp, horizontal = 16.dp), modifier = Modifier, ) diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/tokenlist/DefaultTokenListComponent.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/DefaultOnrampTokenListComponent.kt similarity index 55% rename from features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/tokenlist/DefaultTokenListComponent.kt rename to features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/DefaultOnrampTokenListComponent.kt index 86dfa0a67f..67ada9b94b 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/tokenlist/DefaultTokenListComponent.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/DefaultOnrampTokenListComponent.kt @@ -1,4 +1,4 @@ -package com.tangem.feature.wallet.presentation.tokenlist +package com.tangem.features.onramp.tokenlist import androidx.compose.foundation.layout.PaddingValues import androidx.compose.runtime.Composable @@ -8,19 +8,19 @@ import androidx.compose.ui.Modifier import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.tangem.core.decompose.context.AppComponentContext import com.tangem.core.decompose.model.getOrCreateModel -import com.tangem.feature.wallet.presentation.tokenlist.model.TokenListModel -import com.tangem.feature.wallet.presentation.tokenlist.ui.TokenList +import com.tangem.features.onramp.tokenlist.model.OnrampTokenListModel +import com.tangem.features.onramp.tokenlist.ui.TokenList import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject @Stable -internal class DefaultTokenListComponent @AssistedInject constructor( +internal class DefaultOnrampTokenListComponent @AssistedInject constructor( @Assisted context: AppComponentContext, - @Assisted params: TokenListComponent.Params, -) : TokenListComponent, AppComponentContext by context { + @Assisted params: OnrampTokenListComponent.Params, +) : OnrampTokenListComponent, AppComponentContext by context { - private val model: TokenListModel = getOrCreateModel(params) + private val model: OnrampTokenListModel = getOrCreateModel(params) @Composable override fun Content(contentPadding: PaddingValues, modifier: Modifier) { @@ -30,7 +30,10 @@ internal class DefaultTokenListComponent @AssistedInject constructor( } @AssistedFactory - interface Factory : TokenListComponent.Factory { - override fun create(context: AppComponentContext, params: TokenListComponent.Params): DefaultTokenListComponent + interface Factory : OnrampTokenListComponent.Factory { + override fun create( + context: AppComponentContext, + params: OnrampTokenListComponent.Params, + ): DefaultOnrampTokenListComponent } } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/tokenlist/TokenListComponent.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/OnrampTokenListComponent.kt similarity index 85% rename from features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/tokenlist/TokenListComponent.kt rename to features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/OnrampTokenListComponent.kt index 4bb85444e3..8b924c893e 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/tokenlist/TokenListComponent.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/OnrampTokenListComponent.kt @@ -1,4 +1,4 @@ -package com.tangem.feature.wallet.presentation.tokenlist +package com.tangem.features.onramp.tokenlist import androidx.compose.foundation.layout.PaddingValues import androidx.compose.runtime.Composable @@ -10,13 +10,13 @@ import com.tangem.domain.wallets.models.UserWalletId /** Token list component that present list of token for multi-currency wallet */ @Stable -internal interface TokenListComponent { +internal interface OnrampTokenListComponent { @Composable fun Content(contentPadding: PaddingValues, modifier: Modifier) /** Component factory */ - interface Factory : ComponentFactory + interface Factory : ComponentFactory /** * Params diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/di/OnrampTokenListComponentModule.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/di/OnrampTokenListComponentModule.kt new file mode 100644 index 0000000000..913817a3e6 --- /dev/null +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/di/OnrampTokenListComponentModule.kt @@ -0,0 +1,20 @@ +package com.tangem.features.onramp.tokenlist.di + +import com.tangem.features.onramp.tokenlist.DefaultOnrampTokenListComponent +import com.tangem.features.onramp.tokenlist.OnrampTokenListComponent +import dagger.Binds +import dagger.Module +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +internal interface OnrampTokenListComponentModule { + + @Binds + @Singleton + fun bindOnrampTokenListComponentFactory( + factory: DefaultOnrampTokenListComponent.Factory, + ): OnrampTokenListComponent.Factory +} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/tokenlist/di/TokenListModelModule.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/di/OnrampTokenListModelModule.kt similarity index 53% rename from features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/tokenlist/di/TokenListModelModule.kt rename to features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/di/OnrampTokenListModelModule.kt index 732340f818..7977384f55 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/tokenlist/di/TokenListModelModule.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/di/OnrampTokenListModelModule.kt @@ -1,8 +1,8 @@ -package com.tangem.feature.wallet.presentation.tokenlist.di +package com.tangem.features.onramp.tokenlist.di import com.tangem.core.decompose.di.DecomposeComponent import com.tangem.core.decompose.model.Model -import com.tangem.feature.wallet.presentation.tokenlist.model.TokenListModel +import com.tangem.features.onramp.tokenlist.model.OnrampTokenListModel import dagger.Binds import dagger.Module import dagger.hilt.InstallIn @@ -11,10 +11,10 @@ import dagger.multibindings.IntoMap @Module @InstallIn(DecomposeComponent::class) -internal interface TokenListModelModule { +internal interface OnrampTokenListModelModule { @Binds @IntoMap - @ClassKey(TokenListModel::class) - fun provideTokenListModel(model: TokenListModel): Model + @ClassKey(OnrampTokenListModel::class) + fun bindOnrampTokenListModel(model: OnrampTokenListModel): Model } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/tokenlist/entity/TokenListUM.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/entity/TokenListUM.kt similarity index 93% rename from features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/tokenlist/entity/TokenListUM.kt rename to features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/entity/TokenListUM.kt index c86dc9a2f9..bb9e8f3562 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/tokenlist/entity/TokenListUM.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/entity/TokenListUM.kt @@ -1,4 +1,4 @@ -package com.tangem.feature.wallet.presentation.tokenlist.entity +package com.tangem.features.onramp.tokenlist.entity import com.tangem.core.ui.components.tokenlist.state.TokensListItemUM import kotlinx.collections.immutable.ImmutableList diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/tokenlist/entity/TokenListUMController.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/entity/TokenListUMController.kt similarity index 94% rename from features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/tokenlist/entity/TokenListUMController.kt rename to features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/entity/TokenListUMController.kt index 43c5bb75b8..bb662a5b7a 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/tokenlist/entity/TokenListUMController.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/entity/TokenListUMController.kt @@ -1,4 +1,4 @@ -package com.tangem.feature.wallet.presentation.tokenlist.entity +package com.tangem.features.onramp.tokenlist.entity import com.tangem.core.ui.components.tokenlist.state.TokensListItemUM import kotlinx.collections.immutable.persistentListOf diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/tokenlist/entity/TokenListUMTransformer.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/entity/TokenListUMTransformer.kt similarity index 73% rename from features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/tokenlist/entity/TokenListUMTransformer.kt rename to features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/entity/TokenListUMTransformer.kt index 49678c022f..4ffbf26c31 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/tokenlist/entity/TokenListUMTransformer.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/entity/TokenListUMTransformer.kt @@ -1,4 +1,4 @@ -package com.tangem.feature.wallet.presentation.tokenlist.entity +package com.tangem.features.onramp.tokenlist.entity import com.tangem.utils.transformer.Transformer diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/tokenlist/entity/transformer/SearchBarUMTransformer.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/entity/transformer/SearchBarUMTransformer.kt similarity index 78% rename from features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/tokenlist/entity/transformer/SearchBarUMTransformer.kt rename to features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/entity/transformer/SearchBarUMTransformer.kt index a177307b2c..db17cb19ea 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/tokenlist/entity/transformer/SearchBarUMTransformer.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/entity/transformer/SearchBarUMTransformer.kt @@ -1,8 +1,8 @@ -package com.tangem.feature.wallet.presentation.tokenlist.entity.transformer +package com.tangem.features.onramp.tokenlist.entity.transformer import com.tangem.core.ui.components.fields.entity.SearchBarUM -import com.tangem.feature.wallet.presentation.tokenlist.entity.TokenListUM -import com.tangem.feature.wallet.presentation.tokenlist.entity.TokenListUMTransformer +import com.tangem.features.onramp.tokenlist.entity.TokenListUM +import com.tangem.features.onramp.tokenlist.entity.TokenListUMTransformer import kotlinx.collections.immutable.persistentListOf /** diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/tokenlist/entity/transformer/UpdateSearchBarActiveStateTransformer.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/entity/transformer/UpdateSearchBarActiveStateTransformer.kt similarity index 84% rename from features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/tokenlist/entity/transformer/UpdateSearchBarActiveStateTransformer.kt rename to features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/entity/transformer/UpdateSearchBarActiveStateTransformer.kt index f33d3ea4ee..cf69073114 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/tokenlist/entity/transformer/UpdateSearchBarActiveStateTransformer.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/entity/transformer/UpdateSearchBarActiveStateTransformer.kt @@ -1,9 +1,9 @@ -package com.tangem.feature.wallet.presentation.tokenlist.entity.transformer +package com.tangem.features.onramp.tokenlist.entity.transformer import com.tangem.core.ui.components.fields.entity.SearchBarUM import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.extensions.resourceReference -import com.tangem.feature.wallet.impl.R +import com.tangem.features.onramp.impl.R internal class UpdateSearchBarActiveStateTransformer(private val isActive: Boolean) : SearchBarUMTransformer() { diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/tokenlist/entity/transformer/UpdateSearchQueryTransformer.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/entity/transformer/UpdateSearchQueryTransformer.kt similarity index 79% rename from features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/tokenlist/entity/transformer/UpdateSearchQueryTransformer.kt rename to features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/entity/transformer/UpdateSearchQueryTransformer.kt index 93a6a20675..3e463d159a 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/tokenlist/entity/transformer/UpdateSearchQueryTransformer.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/entity/transformer/UpdateSearchQueryTransformer.kt @@ -1,4 +1,4 @@ -package com.tangem.feature.wallet.presentation.tokenlist.entity.transformer +package com.tangem.features.onramp.tokenlist.entity.transformer import com.tangem.core.ui.components.fields.entity.SearchBarUM diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/tokenlist/entity/transformer/UpdateTokenItemsTransformer.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/entity/transformer/UpdateTokenItemsTransformer.kt similarity index 85% rename from features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/tokenlist/entity/transformer/UpdateTokenItemsTransformer.kt rename to features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/entity/transformer/UpdateTokenItemsTransformer.kt index 0fe02a9f42..b6065912be 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/tokenlist/entity/transformer/UpdateTokenItemsTransformer.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/entity/transformer/UpdateTokenItemsTransformer.kt @@ -1,13 +1,13 @@ -package com.tangem.feature.wallet.presentation.tokenlist.entity.transformer +package com.tangem.features.onramp.tokenlist.entity.transformer import com.tangem.common.ui.tokens.TokenItemStateConverter import com.tangem.core.ui.components.fields.entity.SearchBarUM import com.tangem.core.ui.components.tokenlist.state.TokensListItemUM import com.tangem.core.ui.extensions.resourceReference import com.tangem.domain.tokens.model.CryptoCurrencyStatus -import com.tangem.feature.wallet.impl.R -import com.tangem.feature.wallet.presentation.tokenlist.entity.TokenListUM -import com.tangem.feature.wallet.presentation.tokenlist.entity.TokenListUMTransformer +import com.tangem.features.onramp.impl.R +import com.tangem.features.onramp.tokenlist.entity.TokenListUM +import com.tangem.features.onramp.tokenlist.entity.TokenListUMTransformer import kotlinx.collections.immutable.toImmutableList internal class UpdateTokenItemsTransformer( diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/tokenlist/model/TokenListModel.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/model/OnrampTokenListModel.kt similarity index 82% rename from features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/tokenlist/model/TokenListModel.kt rename to features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/model/OnrampTokenListModel.kt index fb13ef8d9d..efbb04ac25 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/tokenlist/model/TokenListModel.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/model/OnrampTokenListModel.kt @@ -1,4 +1,4 @@ -package com.tangem.feature.wallet.presentation.tokenlist.model +package com.tangem.features.onramp.tokenlist.model import arrow.core.getOrElse import com.tangem.common.ui.tokens.TokenItemStateConverter @@ -11,20 +11,20 @@ import com.tangem.domain.core.utils.getOrElse import com.tangem.domain.tokens.GetTokenListUseCase import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.domain.tokens.model.TokenList -import com.tangem.feature.wallet.presentation.tokenlist.TokenListComponent -import com.tangem.feature.wallet.presentation.tokenlist.entity.TokenListUM -import com.tangem.feature.wallet.presentation.tokenlist.entity.TokenListUMController -import com.tangem.feature.wallet.presentation.tokenlist.entity.transformer.UpdateSearchBarActiveStateTransformer -import com.tangem.feature.wallet.presentation.tokenlist.entity.transformer.UpdateSearchQueryTransformer -import com.tangem.feature.wallet.presentation.tokenlist.entity.transformer.UpdateTokenItemsTransformer -import com.tangem.feature.wallet.presentation.tokenlist.utils.SearchTokensManager +import com.tangem.features.onramp.tokenlist.OnrampTokenListComponent +import com.tangem.features.onramp.tokenlist.entity.TokenListUM +import com.tangem.features.onramp.tokenlist.entity.TokenListUMController +import com.tangem.features.onramp.tokenlist.entity.transformer.UpdateSearchBarActiveStateTransformer +import com.tangem.features.onramp.tokenlist.entity.transformer.UpdateSearchQueryTransformer +import com.tangem.features.onramp.tokenlist.entity.transformer.UpdateTokenItemsTransformer +import com.tangem.features.onramp.tokenlist.utils.SearchTokensManager import com.tangem.utils.coroutines.CoroutineDispatcherProvider import kotlinx.coroutines.flow.* import kotlinx.coroutines.launch import javax.inject.Inject @Suppress("LongParameterList") -internal class TokenListModel @Inject constructor( +internal class OnrampTokenListModel @Inject constructor( paramsContainer: ParamsContainer, override val dispatchers: CoroutineDispatcherProvider, private val tokenListUMController: TokenListUMController, @@ -36,7 +36,7 @@ internal class TokenListModel @Inject constructor( val state: StateFlow = tokenListUMController.state - private val params: TokenListComponent.Params = paramsContainer.require() + private val params: OnrampTokenListComponent.Params = paramsContainer.require() init { subscribeOnUpdateState() diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/tokenlist/ui/TokenList.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/ui/OnrampTokenList.kt similarity index 90% rename from features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/tokenlist/ui/TokenList.kt rename to features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/ui/OnrampTokenList.kt index b8fcb55c6d..c75b080c3b 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/tokenlist/ui/TokenList.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/ui/OnrampTokenList.kt @@ -1,4 +1,4 @@ -package com.tangem.feature.wallet.presentation.tokenlist.ui +package com.tangem.features.onramp.tokenlist.ui import androidx.compose.foundation.background import androidx.compose.foundation.layout.PaddingValues @@ -14,8 +14,8 @@ import com.tangem.core.ui.components.tokenlist.TokenListItem import com.tangem.core.ui.decorations.roundedShapeItemDecoration import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemThemePreview -import com.tangem.feature.wallet.presentation.tokenlist.entity.TokenListUM -import com.tangem.feature.wallet.presentation.tokenlist.ui.preview.PreviewTokenListUMProvider +import com.tangem.features.onramp.tokenlist.entity.TokenListUM +import com.tangem.features.onramp.tokenlist.ui.preview.PreviewTokenListUMProvider /** * Token list diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/tokenlist/ui/preview/PreviewTokenListUMProvider.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/ui/preview/PreviewTokenListUMProvider.kt similarity index 93% rename from features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/tokenlist/ui/preview/PreviewTokenListUMProvider.kt rename to features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/ui/preview/PreviewTokenListUMProvider.kt index 1b735ea3c8..ce2f3e4f88 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/tokenlist/ui/preview/PreviewTokenListUMProvider.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/ui/preview/PreviewTokenListUMProvider.kt @@ -1,4 +1,4 @@ -package com.tangem.feature.wallet.presentation.tokenlist.ui.preview +package com.tangem.features.onramp.tokenlist.ui.preview import androidx.compose.ui.tooling.preview.PreviewParameterProvider import com.tangem.core.ui.components.currency.icon.CurrencyIconState @@ -7,8 +7,8 @@ import com.tangem.core.ui.components.marketprice.PriceChangeType import com.tangem.core.ui.components.token.state.TokenItemState import com.tangem.core.ui.components.tokenlist.state.TokensListItemUM import com.tangem.core.ui.extensions.resourceReference -import com.tangem.feature.wallet.impl.R -import com.tangem.feature.wallet.presentation.tokenlist.entity.TokenListUM +import com.tangem.features.onramp.impl.R +import com.tangem.features.onramp.tokenlist.entity.TokenListUM import kotlinx.collections.immutable.toImmutableList internal class PreviewTokenListUMProvider : PreviewParameterProvider { diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/tokenlist/utils/SearchTokensManager.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/utils/SearchTokensManager.kt similarity index 92% rename from features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/tokenlist/utils/SearchTokensManager.kt rename to features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/utils/SearchTokensManager.kt index ff57e2818c..dde1a1ab4f 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/tokenlist/utils/SearchTokensManager.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/utils/SearchTokensManager.kt @@ -1,4 +1,4 @@ -package com.tangem.feature.wallet.presentation.tokenlist.utils +package com.tangem.features.onramp.tokenlist.utils import com.tangem.utils.coroutines.JobHolder import com.tangem.utils.coroutines.withDebounce diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/selecttoken/DefaultSelectTokenComponent.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/selecttoken/DefaultSelectTokenComponent.kt deleted file mode 100644 index 680be64c0b..0000000000 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/selecttoken/DefaultSelectTokenComponent.kt +++ /dev/null @@ -1,46 +0,0 @@ -package com.tangem.feature.wallet.presentation.selecttoken - -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import com.tangem.core.decompose.context.AppComponentContext -import com.tangem.core.decompose.context.child -import com.tangem.feature.wallet.presentation.selecttoken.ui.SelectToken -import com.tangem.feature.wallet.presentation.tokenlist.TokenListComponent -import dagger.assisted.Assisted -import dagger.assisted.AssistedFactory -import dagger.assisted.AssistedInject - -internal class DefaultSelectTokenComponent @AssistedInject constructor( - @Assisted appComponentContext: AppComponentContext, - tokenListComponentFactory: TokenListComponent.Factory, - @Assisted private val params: SelectTokenComponent.Params, -) : AppComponentContext by appComponentContext, SelectTokenComponent { - - private val tokenListComponent: TokenListComponent = tokenListComponentFactory.create( - context = child(key = "token_list"), - params = TokenListComponent.Params( - hasSearchBar = params.hasSearchBar, - userWalletId = params.userWalletId, - onTokenClick = params.onTokenClick, - ), - ) - - @Composable - override fun Content(modifier: Modifier) { - SelectToken( - titleResId = params.titleResId, - onBackClick = router::pop, - tokenListComponent = tokenListComponent, - modifier = modifier, - ) - } - - @AssistedFactory - interface Factory : SelectTokenComponent.Factory { - - override fun create( - context: AppComponentContext, - params: SelectTokenComponent.Params, - ): DefaultSelectTokenComponent - } -} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/selecttoken/di/SelectTokenComponentModule.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/selecttoken/di/SelectTokenComponentModule.kt deleted file mode 100644 index 620bf54e1b..0000000000 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/selecttoken/di/SelectTokenComponentModule.kt +++ /dev/null @@ -1,18 +0,0 @@ -package com.tangem.feature.wallet.presentation.selecttoken.di - -import com.tangem.feature.wallet.presentation.selecttoken.DefaultSelectTokenComponent -import com.tangem.feature.wallet.presentation.selecttoken.SelectTokenComponent -import dagger.Binds -import dagger.Module -import dagger.hilt.InstallIn -import dagger.hilt.components.SingletonComponent -import javax.inject.Singleton - -@Module -@InstallIn(SingletonComponent::class) -internal interface SelectTokenComponentModule { - - @Binds - @Singleton - fun bindSelectTokenComponentFactory(factory: DefaultSelectTokenComponent.Factory): SelectTokenComponent.Factory -} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/tokenlist/di/TokenListComponentModule.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/tokenlist/di/TokenListComponentModule.kt deleted file mode 100644 index 1e2672c2af..0000000000 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/tokenlist/di/TokenListComponentModule.kt +++ /dev/null @@ -1,18 +0,0 @@ -package com.tangem.feature.wallet.presentation.tokenlist.di - -import com.tangem.feature.wallet.presentation.tokenlist.DefaultTokenListComponent -import com.tangem.feature.wallet.presentation.tokenlist.TokenListComponent -import dagger.Binds -import dagger.Module -import dagger.hilt.InstallIn -import dagger.hilt.components.SingletonComponent -import javax.inject.Singleton - -@Module -@InstallIn(SingletonComponent::class) -internal interface TokenListComponentModule { - - @Binds - @Singleton - fun bindTokenListComponentFactory(factory: DefaultTokenListComponent.Factory): TokenListComponent.Factory -} \ No newline at end of file From 679a54bc0d5e94d39ca7b51ecc6af0f2db413879 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 28 Oct 2024 17:46:06 +0300 Subject: [PATCH 035/106] Updated on 2026-08-14 --- app/build.gradle.kts | 2 ++ features/details/api/build.gradle.kts | 3 ++ features/disclaimer/api/build.gradle.kts | 3 ++ .../onboarding/legacy/di/FeatureModule.kt | 18 ------------ .../OnboardingLegacyFeatureToggles.kt | 5 ---- .../api}/.gitignore | 0 features/onboarding-v2/api/build.gradle.kts | 22 +++++++++++++++ .../v2/OnboardingV2FeatureToggles.kt | 5 ++++ .../v2/entry/OnboardingEntryComponent.kt | 13 +++++++++ features/onboarding-v2/impl/.gitignore | 1 + .../impl}/build.gradle.kts | 28 ++++++------------- .../v2/DefaultOnboardingV2FeatureToggles.kt} | 6 ++-- .../onboarding/v2/di/FeatureModule.kt | 18 ++++++++++++ features/onramp/api/build.gradle.kts | 3 ++ features/wallet-settings/api/build.gradle.kts | 3 ++ settings.gradle.kts | 3 +- 16 files changed, 87 insertions(+), 46 deletions(-) delete mode 100644 features/onboarding-legacy/src/main/kotlin/com/tangem/feature/onboarding/legacy/di/FeatureModule.kt delete mode 100644 features/onboarding-legacy/src/main/kotlin/com/tangem/feature/onboarding/legacy/featuretoggle/OnboardingLegacyFeatureToggles.kt rename features/{onboarding-legacy => onboarding-v2/api}/.gitignore (100%) create mode 100644 features/onboarding-v2/api/build.gradle.kts create mode 100644 features/onboarding-v2/api/src/main/kotlin/com/tangem/features/onboarding/v2/OnboardingV2FeatureToggles.kt create mode 100644 features/onboarding-v2/api/src/main/kotlin/com/tangem/features/onboarding/v2/entry/OnboardingEntryComponent.kt create mode 100644 features/onboarding-v2/impl/.gitignore rename features/{onboarding-legacy => onboarding-v2/impl}/build.gradle.kts (60%) rename features/{onboarding-legacy/src/main/kotlin/com/tangem/feature/onboarding/legacy/featuretoggle/DefaultOnboardingLegacyFeatureToggles.kt => onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/DefaultOnboardingV2FeatureToggles.kt} (64%) create mode 100644 features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/di/FeatureModule.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index b42a58fe8e..5d371ecb8e 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -148,6 +148,8 @@ dependencies { implementation(projects.features.markets.impl) implementation(projects.features.onramp.api) implementation(projects.features.onramp.impl) + implementation(projects.features.onboardingV2.api) + implementation(projects.features.onboardingV2.impl) /** AndroidX libraries */ implementation(deps.androidx.core.ktx) diff --git a/features/details/api/build.gradle.kts b/features/details/api/build.gradle.kts index ce267fe5d1..72035f72bb 100644 --- a/features/details/api/build.gradle.kts +++ b/features/details/api/build.gradle.kts @@ -16,4 +16,7 @@ dependencies { /* Project - Core */ implementation(projects.core.decompose) implementation(projects.core.ui) + + /* Compose */ + implementation(deps.compose.runtime) } \ No newline at end of file diff --git a/features/disclaimer/api/build.gradle.kts b/features/disclaimer/api/build.gradle.kts index 7b97fa25c1..d6fd71c5e8 100644 --- a/features/disclaimer/api/build.gradle.kts +++ b/features/disclaimer/api/build.gradle.kts @@ -13,4 +13,7 @@ dependencies { /* Project - Core */ implementation(projects.core.decompose) implementation(projects.core.ui) + + /* Compose */ + implementation(deps.compose.runtime) } \ No newline at end of file diff --git a/features/onboarding-legacy/src/main/kotlin/com/tangem/feature/onboarding/legacy/di/FeatureModule.kt b/features/onboarding-legacy/src/main/kotlin/com/tangem/feature/onboarding/legacy/di/FeatureModule.kt deleted file mode 100644 index 58798b2583..0000000000 --- a/features/onboarding-legacy/src/main/kotlin/com/tangem/feature/onboarding/legacy/di/FeatureModule.kt +++ /dev/null @@ -1,18 +0,0 @@ -package com.tangem.feature.onboarding.legacy.di - -import com.tangem.feature.onboarding.legacy.featuretoggle.DefaultOnboardingLegacyFeatureToggles -import com.tangem.feature.onboarding.legacy.featuretoggle.OnboardingLegacyFeatureToggles -import dagger.Binds -import dagger.Module -import dagger.hilt.InstallIn -import dagger.hilt.components.SingletonComponent -import javax.inject.Singleton - -@Module -@InstallIn(SingletonComponent::class) -internal interface FeatureModule { - - @Singleton - @Binds - fun provideFeatureToggles(featureToggles: DefaultOnboardingLegacyFeatureToggles): OnboardingLegacyFeatureToggles -} \ No newline at end of file diff --git a/features/onboarding-legacy/src/main/kotlin/com/tangem/feature/onboarding/legacy/featuretoggle/OnboardingLegacyFeatureToggles.kt b/features/onboarding-legacy/src/main/kotlin/com/tangem/feature/onboarding/legacy/featuretoggle/OnboardingLegacyFeatureToggles.kt deleted file mode 100644 index aa692ec61e..0000000000 --- a/features/onboarding-legacy/src/main/kotlin/com/tangem/feature/onboarding/legacy/featuretoggle/OnboardingLegacyFeatureToggles.kt +++ /dev/null @@ -1,5 +0,0 @@ -package com.tangem.feature.onboarding.legacy.featuretoggle - -interface OnboardingLegacyFeatureToggles { - val isRewrittenOnboardingCodeInUse: Boolean -} \ No newline at end of file diff --git a/features/onboarding-legacy/.gitignore b/features/onboarding-v2/api/.gitignore similarity index 100% rename from features/onboarding-legacy/.gitignore rename to features/onboarding-v2/api/.gitignore diff --git a/features/onboarding-v2/api/build.gradle.kts b/features/onboarding-v2/api/build.gradle.kts new file mode 100644 index 0000000000..d6e9adffe8 --- /dev/null +++ b/features/onboarding-v2/api/build.gradle.kts @@ -0,0 +1,22 @@ +plugins { + alias(deps.plugins.android.library) + alias(deps.plugins.kotlin.android) + id("configuration") +} + +android { + namespace = "com.tangem.features.onboarding.v2.api" +} + +dependencies { + + /* Project - Domain */ + implementation(projects.domain.wallets.models) + + /* Project - Core */ + implementation(projects.core.decompose) + implementation(projects.core.ui) + + /* Compose */ + implementation(deps.compose.runtime) +} \ No newline at end of file diff --git a/features/onboarding-v2/api/src/main/kotlin/com/tangem/features/onboarding/v2/OnboardingV2FeatureToggles.kt b/features/onboarding-v2/api/src/main/kotlin/com/tangem/features/onboarding/v2/OnboardingV2FeatureToggles.kt new file mode 100644 index 0000000000..af4b3152e0 --- /dev/null +++ b/features/onboarding-v2/api/src/main/kotlin/com/tangem/features/onboarding/v2/OnboardingV2FeatureToggles.kt @@ -0,0 +1,5 @@ +package com.tangem.features.onboarding.v2 + +interface OnboardingV2FeatureToggles { + val isRewrittenOnboardingCodeInUse: Boolean +} \ No newline at end of file diff --git a/features/onboarding-v2/api/src/main/kotlin/com/tangem/features/onboarding/v2/entry/OnboardingEntryComponent.kt b/features/onboarding-v2/api/src/main/kotlin/com/tangem/features/onboarding/v2/entry/OnboardingEntryComponent.kt new file mode 100644 index 0000000000..93b12a1d21 --- /dev/null +++ b/features/onboarding-v2/api/src/main/kotlin/com/tangem/features/onboarding/v2/entry/OnboardingEntryComponent.kt @@ -0,0 +1,13 @@ +package com.tangem.features.onboarding.v2.entry + +import com.tangem.core.decompose.factory.ComponentFactory +import com.tangem.core.ui.decompose.ComposableContentComponent + +interface OnboardingEntryComponent : ComposableContentComponent { + + data class Params( + val todo: String, + ) + + interface Factory : ComponentFactory +} \ No newline at end of file diff --git a/features/onboarding-v2/impl/.gitignore b/features/onboarding-v2/impl/.gitignore new file mode 100644 index 0000000000..42afabfd2a --- /dev/null +++ b/features/onboarding-v2/impl/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/features/onboarding-legacy/build.gradle.kts b/features/onboarding-v2/impl/build.gradle.kts similarity index 60% rename from features/onboarding-legacy/build.gradle.kts rename to features/onboarding-v2/impl/build.gradle.kts index d82dd86a7d..e0028b37e7 100644 --- a/features/onboarding-legacy/build.gradle.kts +++ b/features/onboarding-v2/impl/build.gradle.kts @@ -8,21 +8,20 @@ plugins { } android { - namespace = "com.tangem.feature.onboarding.legacy" + namespace = "com.tangem.features.onboarding.v2.impl" } dependencies { + /** Api */ + implementation(projects.features.onboardingV2.api) + /** Core modules */ - implementation(project(":common")) - implementation(project(":domain:models")) - implementation(project(":core:featuretoggles")) - implementation(project(":core:datasource")) - implementation(project(":core:analytics")) + implementation(projects.core.featuretoggles) + implementation(projects.core.analytics) implementation(projects.core.analytics.models) - implementation(project(":core:utils")) - implementation(project(":core:ui")) - implementation(project(":core:res")) - implementation(project(":data:common")) + implementation(projects.core.utils) + implementation(projects.core.ui) + implementation(projects.core.res) /** Tangem libraries */ implementation(deps.tangem.card.core) @@ -33,27 +32,18 @@ dependencies { /** AndroidX libraries */ implementation(deps.androidx.core.ktx) implementation(deps.androidx.appCompat) - implementation(deps.androidx.fragment.ktx) implementation(deps.androidx.constraintLayout) - implementation(deps.androidx.activity.compose) implementation(deps.lifecycle.runtime.ktx) - implementation(deps.lifecycle.common.java8) - implementation(deps.lifecycle.viewModel.ktx) /** Compose libraries */ - implementation(deps.compose.material) implementation(deps.compose.material3) implementation(deps.compose.animation) implementation(deps.compose.foundation) implementation(deps.compose.ui) implementation(deps.compose.ui.tooling) implementation(deps.compose.coil) - implementation(deps.compose.shimmer) - - implementation(deps.tangem.card.core) /** Other libraries */ - implementation(deps.compose.shimmer) implementation(deps.kotlin.immutable.collections) implementation(deps.kotlin.serialization) implementation(deps.timber) diff --git a/features/onboarding-legacy/src/main/kotlin/com/tangem/feature/onboarding/legacy/featuretoggle/DefaultOnboardingLegacyFeatureToggles.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/DefaultOnboardingV2FeatureToggles.kt similarity index 64% rename from features/onboarding-legacy/src/main/kotlin/com/tangem/feature/onboarding/legacy/featuretoggle/DefaultOnboardingLegacyFeatureToggles.kt rename to features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/DefaultOnboardingV2FeatureToggles.kt index d46fdf1c92..4245576b3e 100644 --- a/features/onboarding-legacy/src/main/kotlin/com/tangem/feature/onboarding/legacy/featuretoggle/DefaultOnboardingLegacyFeatureToggles.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/DefaultOnboardingV2FeatureToggles.kt @@ -1,11 +1,11 @@ -package com.tangem.feature.onboarding.legacy.featuretoggle +package com.tangem.features.onboarding.v2 import com.tangem.core.featuretoggle.manager.FeatureTogglesManager import javax.inject.Inject -internal class DefaultOnboardingLegacyFeatureToggles @Inject constructor( +internal class DefaultOnboardingV2FeatureToggles @Inject constructor( private val featureTogglesManager: FeatureTogglesManager, -) : OnboardingLegacyFeatureToggles { +) : OnboardingV2FeatureToggles { override val isRewrittenOnboardingCodeInUse: Boolean get() = featureTogglesManager.isFeatureEnabled("ONBOARDING_CODE_REFACTORING_ENABLED") } \ No newline at end of file diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/di/FeatureModule.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/di/FeatureModule.kt new file mode 100644 index 0000000000..58f1c6cd79 --- /dev/null +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/di/FeatureModule.kt @@ -0,0 +1,18 @@ +package com.tangem.features.onboarding.v2.di + +import com.tangem.features.onboarding.v2.DefaultOnboardingV2FeatureToggles +import com.tangem.features.onboarding.v2.OnboardingV2FeatureToggles +import dagger.Binds +import dagger.Module +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +internal interface FeatureModule { + + @Singleton + @Binds + fun provideFeatureToggles(featureToggles: DefaultOnboardingV2FeatureToggles): OnboardingV2FeatureToggles +} \ No newline at end of file diff --git a/features/onramp/api/build.gradle.kts b/features/onramp/api/build.gradle.kts index dfd87a2e88..a241d32468 100644 --- a/features/onramp/api/build.gradle.kts +++ b/features/onramp/api/build.gradle.kts @@ -13,4 +13,7 @@ dependencies { /* Project - Core */ implementation(projects.core.decompose) implementation(projects.core.ui) + + /* Compose */ + implementation(deps.compose.runtime) } \ No newline at end of file diff --git a/features/wallet-settings/api/build.gradle.kts b/features/wallet-settings/api/build.gradle.kts index 7c48ae5a59..aee60dc9be 100644 --- a/features/wallet-settings/api/build.gradle.kts +++ b/features/wallet-settings/api/build.gradle.kts @@ -16,4 +16,7 @@ dependencies { /* Project - Core */ implementation(projects.core.decompose) implementation(projects.core.ui) + + /* Compose */ + implementation(deps.compose.runtime) } \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index 2876b342f6..251a4e76a0 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -151,7 +151,8 @@ include(":libs:visa") // region Feature modules include(":features:onboarding") -include(":features:onboarding-legacy") +include(":features:onboarding-v2:api") +include(":features:onboarding-v2:impl") include(":features:referral:data") include(":features:referral:domain") From f71aa5ea1b196888ca5f4af9a2d3d9d9def9c4a9 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 28 Oct 2024 19:36:21 +0500 Subject: [PATCH 036/106] Updated on 2026-08-14 --- .../java/com/tangem/data/staking/DefaultStakingRepository.kt | 2 +- gradle/dependencies.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/data/staking/src/main/java/com/tangem/data/staking/DefaultStakingRepository.kt b/data/staking/src/main/java/com/tangem/data/staking/DefaultStakingRepository.kt index e57c15dd67..1a40afb7ac 100644 --- a/data/staking/src/main/java/com/tangem/data/staking/DefaultStakingRepository.kt +++ b/data/staking/src/main/java/com/tangem/data/staking/DefaultStakingRepository.kt @@ -645,7 +645,7 @@ internal class DefaultStakingRepository( Blockchain.Tron.run { id + toCoinId() } to TRON_INTEGRATION_ID, Blockchain.Ethereum.id + Blockchain.Polygon.toMigratedCointId() to ETHEREUM_POLYGON_INTEGRATION_ID, // Blockchain.Ethereum.id + Blockchain.Polygon.toCoinId() to ETHEREUM_POLYGON_INTEGRATION_ID, - // Blockchain.BSC.run { id + toCoinId() } to BINANCE_INTEGRATION_ID, + Blockchain.BSC.run { id + toCoinId() } to BINANCE_INTEGRATION_ID, // Blockchain.Polkadot.run { id + toCoinId() } to POLKADOT_INTEGRATION_ID, // Blockchain.Avalanche.run { id + toCoinId() } to AVALANCHE_INTEGRATION_ID, // Blockchain.Cronos.run { id + toCoinId() } to CRONOS_INTEGRATION_ID, diff --git a/gradle/dependencies.toml b/gradle/dependencies.toml index 9bf5436270..d010c3fb81 100644 --- a/gradle/dependencies.toml +++ b/gradle/dependencies.toml @@ -88,7 +88,7 @@ markdownComposeView = "0.5.4" # endregion Other libraries # region Tangem -tangemBlockchainSdk = "develop-835" +tangemBlockchainSdk = "develop-836" #tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds tangemCardSdk = "develop-391" #tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^ From 4aaaefbd71dde36e84b1af85d84b9bcb294ceea1 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 28 Oct 2024 16:31:30 +0300 Subject: [PATCH 037/106] Updated on 2026-08-14 --- .../wallet/domain/Wallet2CobrandImage.kt | 18 ++++++++++++++++++ .../ill_crypto_casey_card2_120_106.webp | Bin 0 -> 25890 bytes .../ill_crypto_casey_card3_120_106.webp | Bin 0 -> 43166 bytes .../ill_crypto_org_card2_120_106.webp | Bin 0 -> 26526 bytes .../ill_crypto_org_card3_120_106.webp | Bin 0 -> 32680 bytes .../drawable/ill_stealth_cards2_120_106.webp | Bin 0 -> 10750 bytes .../drawable/ill_stealth_cards3_120_106.webp | Bin 0 -> 15840 bytes 7 files changed, 18 insertions(+) create mode 100644 features/wallet/impl/src/main/res/drawable/ill_crypto_casey_card2_120_106.webp create mode 100644 features/wallet/impl/src/main/res/drawable/ill_crypto_casey_card3_120_106.webp create mode 100644 features/wallet/impl/src/main/res/drawable/ill_crypto_org_card2_120_106.webp create mode 100644 features/wallet/impl/src/main/res/drawable/ill_crypto_org_card3_120_106.webp create mode 100644 features/wallet/impl/src/main/res/drawable/ill_stealth_cards2_120_106.webp create mode 100644 features/wallet/impl/src/main/res/drawable/ill_stealth_cards3_120_106.webp diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/Wallet2CobrandImage.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/Wallet2CobrandImage.kt index 1f191d3585..21890462f2 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/Wallet2CobrandImage.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/Wallet2CobrandImage.kt @@ -61,6 +61,18 @@ internal enum class Wallet2CobrandImage( batchIds = setOf("AF28"), ), + CryptoCasey( + cards2ResId = R.drawable.ill_crypto_casey_card2_120_106, + cards3ResId = R.drawable.ill_crypto_casey_card3_120_106, + batchIds = setOf("AF21", "AF22", "AF23"), + ), + + CryptoOrg( + cards2ResId = R.drawable.ill_crypto_org_card2_120_106, + cards3ResId = R.drawable.ill_crypto_org_card3_120_106, + batchIds = setOf("AF57"), + ), + CryptoSeth( cards2ResId = R.drawable.ill_crypto_seth_card2_120_106, cards3ResId = R.drawable.ill_crypto_seth_card3_120_106, @@ -134,6 +146,12 @@ internal enum class Wallet2CobrandImage( batchIds = setOf("AF19"), ), + StealthCard( + cards2ResId = R.drawable.ill_stealth_cards2_120_106, + cards3ResId = R.drawable.ill_stealth_cards3_120_106, + batchIds = setOf("AF60", "AF74", "AF88"), + ), + Trillant( cards2ResId = R.drawable.ill_trillant_card2_120_106, cards3ResId = R.drawable.ill_trillant_card3_120_106, diff --git a/features/wallet/impl/src/main/res/drawable/ill_crypto_casey_card2_120_106.webp b/features/wallet/impl/src/main/res/drawable/ill_crypto_casey_card2_120_106.webp new file mode 100644 index 0000000000000000000000000000000000000000..5aaa6f9477cf3ed7f3fe56e60469b95f39e76a84 GIT binary patch literal 25890 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 z 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 literal 0 HcmV?d00001 diff --git a/features/wallet/impl/src/main/res/drawable/ill_crypto_org_card2_120_106.webp b/features/wallet/impl/src/main/res/drawable/ill_crypto_org_card2_120_106.webp new file mode 100644 index 0000000000000000000000000000000000000000..3c042caa95954d5f7d5b3e20796b2bd75663fc8d GIT binary patch literal 26526 zcmb4qQ;aSQtnJvgZQHi(*<;(bZSApb+n(8D+qOO5`5$g>&g*H?r0GMG_GP83t18kG z66qE|K$_wrN*YRBS};IBKuG`j9xzZjFp#jEl2io*5D+{byB#hQeZcWod}IkY452az z(10zGyjHew3L7eh4~IdU-`n$2+f8?dpWxT6+16|CYPVgA+$vQWYk#EUep?*r#@d~) zDyNzID=5-d8#3AG$FFQ(N5~*m-nc|4X={DSm_H9!0l@0koh{2RaJ8vA@jyVfW$SeGzE_#~#xUQ49O|}0<#(7I zJYge6{cF2~yWcKy4u+kvrd;mjW26m}{|J2^K@(~Vm}?U`Yx`Dbh#Nj?^a}GH5#__| zMCz^w`ih<`V_h8QniYORqFbV>S_WRLH-O^yk{p~aGTEMz50XF1u3LH}SU^yZI3)FW z3Q*|tYq7H`r8=3iJW=ESJb>6)rX;UAy*RT)mX#1kE!`qi5HHbC*>xAQhcBhm-{tt{ zzPB3J6>g=g%fuaC*se|@5(lqos4Xp(+Op+b7VceKk%Q$~MQwChxyMp)W`${RWaX#0 zA?R6)oVUpcbMeM34z=ASJ0av!t?G1LaHOzeNjQ9L9Did>GdaZ~Jy^23$wx#dOB~d+ zZNKy92?-_FQZvc{+HllX&pJC-Ltwx}koCg1csHHD z8BILp_D+`Y*X!2mBi8oWwRs?vSz%_&~yj_O{s$#_a#97t~mv1F_Ty1=n_6R| zhdXtFm57$s&)li15~3?Yk1H0~^H&$cgq{EsM!fgLMgs@fn3T}?3g)`Wwpycih~#i< zAUxlKLfye*#$8*Bp!RC-0>#Q=ZO6_Xly+q~>bp^r+~l!m71W3fg!_9N3yG=|3fJ-v zC6G|_y@URps*ayYF64Q7<`&0mmVlWBiBS7JLh1J0dNa_;5UVss^4 z8k2rf0?;;7#V z@$#Y`11(p@DHeSR$0H4O-#DO@U6L**=}v;P4$}+wu)DOVdSOn+u_D^y=TrJwpSorj4T6Q8)&S4As_!NP$=cYrnwn=U+%_A`9e+|! zWc{rvh24=ms1nXw+k+pO@^-yt^1c#Id$Wf)o8z@eg}F539b|1!a7K)sp2r3MIH}n? zlcgrrU;hhcj&IFoUOv(xT=%^9(@{A6VMwer^nB|B^bfG$jIOQ1hfELEp{f`m6@GpH;|0m^gM80+UmSBT*y#L2M|U{EKSdn!>hV?lAf|D zTZ0wR_PvZAgZ`>N8MKm6wk4DfB>lndIJGZsJ6MEXJp8ZFMw(1r$ro*G|$H>!id;V@T`1{Gs=|OYGwm)?0JVw7v!#n85 z!qy+WtGuwu>DFz-S_Fd!qk+C8e- zqD~zlik69ucKP>Z1Q=RV!rT5ux4D@Bf^WrR%v;Vc!K_=ir~VcG=bv7H`dg<9;Ey@u zNqezZR) zJPGdp0sw$tb-)(D3c$M)@Ts_==!keixCl7?_3_>EE(62>3a=5rpS}ry8Lj8O2_FN- z{2zW80TuvhfX{dN9pk*mWBpaY4S=IR0Pyh(6ma=E^b34R{0-VF`fc&nUuv*VDEzzh zTKsS{Yp@d#^#k}uT<=d}{9qP*A$<3M^;vrp+%lN19|OFf6rF0z5$!wf-~!Wr*H4i`v1D{ z_{g7B^aD))J_gjjL;b4%DE^GU?L7h-{1E}}{eZ93--eiAS=?R|j~e@zxZTF?RZj1* zJFT6o|4Zrt4(XTWFKT0l@MW_!v1>E1azK_#Uef<7RNEO61l#rUFEzkLuvmFW{>(s) zSwkj`QN7l(RBiWzOu57BCyz>xMMD|ILJh%lefhoL*Z;*~k1(iLr!5V25jZv%k}Cv( z?>C>&-8*3RXb%cZJZ`1uiLyAv2YUbu$i~5e+O71#K;=!TA8u~%;t#^Q5ECiBOi1y)V-g^iqf zXJs3O*e*vLDQS~G%YCB1ZM2g8rx#zLx_jp{pmm_GZy-@RGTH{O<#?#ciV5)3UJC(het}`=hs|rO?C3_Fy~-o#1QLJSv+rR zeW6ds+7VsoL?ux(v1q2#^nFs5;J@^eZ?f}x0tiBChyflr)jr}{t0jcEy~dEk#h*Zg zx2>4zLKTilfPk19HpIpHfzNE4Mr7>R+0|QFAxr=+^ydIFIN^ z!v$9V3Kxu1H$_tra10I*4RTOhtLR}80p}k+43&NS>5#}m(6Moj=1(_*6Swrmhmv)X z8#+veYQkQYB;a3eMO!=4+V?SbRd#AqdBNk%54x*tEuvhZkxtlh;B4F;uX-!1Sw5Yr zoPpIN5D3)RWaLGvCmq-pDXi0E3C0WmHYD_roI}yyP~Ym&3E~)Ek*a^)?cnaXdW8x< zmJHRQUN{5 zR|jpP9TsNvleKo?8IDp#1H0SOKVVEutKPZ};r!=|Qhd9z0J&=@3ZHcvEcE!^_oqBr z8g>{{nPUs>9X*Os^EEQJBwd@XdVgCRA8^onV2y*eF5>b zqo#-oK{PI?Sfst(VsM(}WHLh7LCu}3MD43a1uWBANHFopk{#pU&L-B=8SEyG(x@N5 zip|4SyP`IB^!Xkd1}7f4Zf#WPrEmzL&a^jOYqgCbtv*t8Z%?jcjPV9q8W9W8&$zPq z685QITKGR$Bb|Kz-p-J#&O;p~Bzu0Un_FF!EJZSULg-bogj*D1@j1pw3Evx)G{TXu z!L@+*9~Tcq(|29PJUoU^%KLKYl>?gg3Q_59CA~o)f7MHqDGeJrnSZoFUn&_?WJVYq zj4Hwi)R+>se$i%!PTimZWY|Vm(;CY`~Z9wK)2&ip(i902Q5ZA^mXhb-^l9038?nDRD1so zr8F}`{GYS@gq(5Dnb7c1R~_{kAB(6f#hfq34;Y4@LTdRxVVu74CZAE^@1s0H2zbU{2{$kMopdb}dQr;WFF>GbhJcyiYS79$TlPQ&TJWH70f+ zyd1~Us*9kawsLKBmI!4m?;Aw_x<~X!mv1icxM!!GjxCIief9CDA^P9AUns#MbW;Il ztny@M4aO~y;uz?lSqH0wE3$;_n1skl!J(@TJC$2R+vZ!<4JoaeU|ARnHs+e$i97w> z0K5D9a>j?tt(RQ$`^u8rb$1OH#e6x9H4T}N{ltJL{i^zVaX|Ed7ESv+5PAEHeUQ>} z@)pOLX}{ul1Nab<8jxPE4mc7+6Mt?!CJNwFg!D-gIwI!q8j*I;??L`TYv+i{kKdxj zS1{<*)h>HX;LaYFkw`i!{c5e1J9Js88A~ES_+yE;r5US&tm`{QdTP`Zqi&qcwkw2= z+xEs}b1RH*9=oDd`ELT&dYV2`E%4Q&m7eRMUF<{0QC3kk{9trg8Pb-EIJ92bYU@s! z!sdxxs?T!6IXE+C$dYN>Khz>jt5vVGz05}mIscpuiyVPQUZfQ`2mplYP=k30$4593 zYM3)x7v6AzWv5R7f_604O{i1-OZUenYL@H z7qN`XFAch96#k%5mn~IZA#j>~7#{hBilWR5v>{>FmKx1w>^-1zf;YeJwov};b(uIJ zzNl%Ql;EKLrF%B!&XmoT66kganH>I7mmC7EwwX~U_P0OgR)E6KJMPa#w{38tPnT=C z@_iFQ#$cZZQYK(>P)a@)1^z-m+^&|4gHR<6jhgrd|M|<0o+U+9Qsz3{jHtP_Qb6^5 zSLOn_hZAyZUb3iUy79-z#OxBIZ|tRHP09-Y>@gxg?g>RM+!Uwh!n^T~+*^V{gE+B(hAB zY!eeU$9YX*oo6Bc+JT;{xHm9^)k8YD+D!T%8~@{D&Yp#H6ao^0H4+^6`7 z(XG8GL5fPh67PBrL~lB5A0M~7NcoH`se%+UHBCdLL}jw0yi_3gL))yod=+##x*P6C3!BS@? z7j&aC376I%T_kd<{KLCrv7$ITzD*{975_+6rH{^dt#5cZfmd>!DDJ}3nUNt?+Ap&D zuOVJ_vJglyXUYx|2>+Jpwv}GNr;j&FB6F^xo zXre4O_F~9_gvy8=ZL1^_YKzD+HvsO_T{)e`g*!Ec$nBw7oDF=Z1cc1M97>Jx16r|z z{xX`p0qRf(g#UQMutOUA9>-+3n5n-4*D745e&L&j*u(3;lnC`a0Iq-_`*_}$Fx+1L_TNeZF7nzLriq#f;ruAoz4jzOH)fNhtPwgy$5YYd4G_Rd zbfWKWbrFuSfwZw|7|qK`W4&R0VQ8AGTibgC0P=Tn$HpbxbFWPW$@}weJP~00^3(O1 zaeah5T8v=PndF$ILxr9^ffG4X7k$94TRUX00dr!`F9>o9aw$JM1XxU75-9W5k}V@x z6NLLjlHQTmKX<32)q@k4>H+Um!7KVs!YG3_+TxeI=57vzd}8NH<67)7w;k9i;*?yM zobk)JjXQ+mGko?J$)z@cL9Y+#{Ia7Q%ob2?6ci|H#^2HJwS)R$SiYxRz(>BqCx_28 zj2*lSh3_JpS7@u-ZEPR9lAZkJrT$dh61X)fFTeQP9TN};;crLmumyPaWpOxpEy--v zl!^M@BQ<$>PI*Duk;O0SY(#7z7;Ed%>AzAr;)jp~OT=DHIbdPIUlj)BwKk4>Zo~sN zdW$bL583C$8|4FC`t30Mr$_u}X8Xp$ku{U*NF~&W*Tt>bfbedBlXoDS^&aV0Q8}?S>cPd^vUBA>)$e)Asg1kq35zm7|m6zse3{!p$~WC{hm$9bXuq2&&l#l zJu$+(O*Xs-y66EXp>ZeCIe@M@YG9k^b@hQ3SDT^I<%_#-UZ}#ZYhUIV4fs zBtDpZUN`P-iZpZlL&5hepfn#6*Nw*>B2}a`#qORxU{9gE=YD@g5j-thA7&G=MA^&Q zq{t2E?4N~|=UQ~bqDsNJc>Wyxj6!!%YHE^}jXMhJ?3l0VlT*ez_}^J)^*PIog{bak{PAB&W`-hMWG$7x=|Uv1bar)K}4uLVtNuaST~#%-vT z{W6fNkn5D7=hl|fvf;dXKFjA@h!HM&d75Ya*QK>^1KgE|MyYalaD_RVXY?4P#<|f|y7}87+#oNz4Z@mlhe;zZiAT1gc z4q*d)7~}l%zG;*_8+J7D=Uina3$puHnGWCr{W(3_J1{~Gl6Fv@fRAMU!t>G62eGS_ z7=_~j%Fm3dF!w6|dNf|_ZCzZ#4JUZyU)q@kLKaEC9hFsM(&8_00qu*~q-1sFEgO(Q z^}Z|(wNlg76_j&1CLD@5US%iP;+jF*i#k!|UYC?iz7vmxX(zB#mGl-*?HPto(~lFt zmP3}$hY^RN3bCF(%B!^qpBWVwE^kW$#MBJDkYiAx0*hY2zp$%AXI}F>2C^}*ej8L3 zny;cIKuz%kxMZ% zgx>o&;+tQS`8d-*?vc5vB3`Enw-cPS+|_a1xAu^QF2JisHG*pKGP|OQF6(|X^~p8S zI4kLQiq4=EuX`7Wqef9;XIX}+slb;pOVJn%In88#TP(joIe1?0FP&VIJ*aVQ#tK zRxCZ~MIJOx9CdDgiKA`7=?na}E-3z#jX5y`>ATdFbcyz3#>42s&1lbVA3mx(;sCP0 zj=i8(H1=quw>GFp=p)B#g88|gd}3@}+T@Q}tjQ!GeZx75i`UH@&$U25>IAl@&2&VA zj3*IJ_G{L3c2mneP;)KY(U|sO8Tg28nyXW*546}y`iAy;GmP7Kh#Q|h1&`3g2B>~b zq*h7RQ~jDe4^rtsf<;|03h0m^7%1yRC8CqzTp_->9hHcAYiyO*3qJVrZo0=od9$&i zF_8&!(oz>mZ3hw(l$g%iiJEr3ceVnW*FNz|5Tt2ms4tV8kSovolG|K9k&{fX!4Ljk|RI+}lcI zs&d;AEJ9kjCYAIou&(YE-VFuGq$RX~Xt%_mXO0JqM<{qj#e*!!YT|m$iyI?H`NX}~ z1EWrMTvcI3|fVW|F^7YYe`nf9~^hDx6@q zN~*^9Pfy5vlBwaPbvXiuX{G5;#9=oFhK+>VZ>uK9eW<8`rRPE1j7Ez|;vgUZ=c&&u zc!q+W*5{K4Z#+@sB?QlZpHn}M@oJp}Hy8aNDk%B&H z3n1j;tif%QyEDSoX+s|;QVj0GMcXm|<~5f0T$SR;=aoPDkk=sq;1l_y#P`q#+n z+X@Ju4UI0+K)IZy(}p_b09K8%9u~k4MhLB6B$Rd++PLYJQ$vfQ2U((f(6s~ zN%+=*%OAb9WQS+$;k$3E+lT)kO{zpycy=HDb{-KTG_x+Hikk+K@_s zhB>O{e@7ZH&0$hnNvPC ze)#+GU!J~g$^I54;M#+3lPiuLi_u&`!+%8XXyWeH7w@yT?;Fykvn{3)jgm05wLVD~@z#3HBbjOYj=L?=7G4N1B2%{|AKQrqAvn_{3{@>BU^q%F zYU8bNmXlZ$N9W0^4C0S?P#|AKYeCOf){+&?-WCWP)cV%!mH&GDq067yvaE>*&cw~V zMA3%AZ5FGS)e_y5e*=av--x=vwK>JP@yWNd;&SLh-O<>ub*!DrNwSROh+2XygnUnteIeSnUw+3q*5u{9s#+8iN7bnou4k2YbQ3UR)R! zb^c2ab^4jc$5b@IyEV*_AvO5O@2+Q%3Kd0>vOQ6)u^yaPD^o*O|K9!X>}10#ZQS#S`JT#Jr)S zS$S`gkqo2-{`f#m_^xWN&Oy6!C--007VA=h?4a`fxRxp#w4xk=odNOG6+IHaYqBf7 zI>F8cqrco(;F*0iXgYQ{B=>1;(`hQIu6&a&<_sNfwWBD`eK;58M=T=u#>7?B-H9m@ z;}qC}(TY#r#9PIkfKlSm7})}+MrHi@#;Cb|stXS83rU+O)tV1KM}7a}K;90{6n zq+b$4!(OGT_N)GxkYnba#1@>0;J2<>>uAdm6~{fL5Bu?^;mY1@nk^tTBCJ5!+}^X zypdIp@97iEXkHGEjL4{VBl)at_VO^1)5!jONG&vuU!hqjK-2KyQI8D0vBkB+M8mM* zM~ijG!dTs|O8IIt=^Qz$?V0A%y|eNw+GHLl6y)ZBX7~%G1Z02}sz_2as|)E^;=1@f z1mFLLD*K{pGtRhTFwpxe2H(lBsYU$o+c%Z>ceH~=GE{&;vir^&tuXF9sDA}#d@Lj( zra4JzeqnJ#Fe%zsr&Jy%2nIO;alX+;KIM`?;iNTZ8)=%o7%_#ox4gy z2|~u-(LN;dLLzW1gKUOraoHN{!+=0N$)ELw#l)4Xdjdk%CD?b;FpSo*d41}jtIB!s9Uk^y+~hMm_#EacrKWY-wSq07m#i9hZ;+r(3~5CulXSr zceaR9tkg86+I>gWxVgV#2VRJl%3tQic2HUi6FrSuFDzEINApX#aXopMVDIA4+wKbuDMyV7+(tzdxO2NL>yH%ML$-NYjPVV7@t5+1gU!iwL(c zC3pl9Il8gtzyT|p-NO-1#8gNN!`<;LnYQC$`2x;ENBjGbp5lRlhAa-veO$`a(e z!}_zRYf(qB-S4g`Bn*5lb~N4jnISLz0dx{BHf0n?FFSmCDR|dTSpUuHAcX|{B#?s} zClFnXc6-BO{Crlm!nu2`-!2a~l7l11TUoqu!^W7^M?tfMdnIipy1%JM%wQ$MQM`m0 ze)FW^mVN==MxPY~prpIxXAO}t87n)!`t~n9%7QQZ8G>_!%3+Afy$t1#;KWd|apMt* z1;%bH@T7=8(xKb}U2vsUPiYn0JkuiHeir~n`1jFZ!A*0&I_Qk}OY9#`C-2od`C?C5 z*PVWdFDjz*$UT0qmHux>2O*gDh}d-UaaGpX0Ab2eO>1IUl<@8-Dd6waJjtz> zy*<0o1Ob!sIM6>yYDA}lLDT!o?BZqk%beg}V`vEfeEJ~i`_K+gIJ1FPCsJo{^GKjEhOaXKiz zyNG^CCS_95Q6<3iTwjXN90vuJNUDGs-b zX>2;`BDI?U++>>?CF@xGuv41O#x1QI^_>nOmMSC6>gmo(vb`{CX!y}3(u6ApJj3B8 zFxaOIz7D_Jwv?NQMk+25DrigzY9YereF|b9kieJc0$gB*+hh@9E=yMKy`ZQ^E~cz@ zszS3kxrbzNeq4wXOOgr>SgGltmU*Sgq8a26;30b}jMgsh9yYkT{{0nMb16)V{Cq$} zrorZzl(MEm@3eb`jVx*zRF+;=hL>;(2C4o?hLPPEFo^(lQ+NlzSPWVTZnH(-N~Q00 zP8Ub_AY>WH=Z4~V`c@7w|6Ij_d+oXD-aU0jSw#y6x9s4k|mg_hYMn+ zc_dsm-Rx2eE{0A?UgULbmeEzraU)6LK-t0l`{-)y0ywL0z(+dEuBAw#F^QQ*0*;|A zFC88wiqdMdxcV0x3K03#a`4)2UhWMhRMS~$s&0|+v}{EW$8FAqjS|+|4i@!An zim;oV@*^bEjX0Gh?8kxXDn=R4d1sy&ZJa zUBL2J?(rmEhL-reqBH&cbNDX>m_7@0^2(2ozyC~q|k#tALmI* zPvI>x*?b+uGKfXBHAhX#p@X|*>=Q%edabtD^dR>&AxMQ~AeNm+#2H+mMQ-(<_4Pf; z=t6HISMx;;6F+w-iT@4{Kb%Pt}SM z&s2&J%y9xHVL5c3RTpciuu`Hda=W5s^%3$Z^XTE^1q%PJXV!yS!4ST8N{?VOa<)kj zQExo)Gy4UK`VV!^er{i!Ue{*`G(}!tI_^&_N9c&1{t}OFvn>uE!t@14nabTr(?y3f`BCx2(`lt|#>e za8INY0}G|XEMh(nz$;gzAo&vg0-0Jjq0gNzb>C7oz0ydxTe+%2Z?6TjY@0?2k`Wt) zFtT?%vzJac#P*BXriYLhSt6Z3?EdjQV=}>?)wZ8Kq}R>Wd`i?fcZWw#;3pmLspF_7 z#9s#|)+$gW@c1Zhh;oJO*1zh!<>;RacR{2*I|V#3qC;yWubwcBv<`_99j-q!Xz3_j z8mP&vX_g7&#wq4l%x6($i2~nFSqc~)&eoa9#?H`W?d6?;m7{B_g2mEBpJY~Xskdl( zl5>~~>T@2VjcgUO@j8?|BI=nBEJ{UnMiiON+d?Ioc|rf#ODB)a^1Tw51PBKoM#t5M z_(A%5x>&T!^v~SB24)l6E_#6+LhKcWm>8{Lbn{$ULePIl4&pWr@R$+hr$f&;H59}4 z%(X%_I!Uf{a5RcuewR6s2zAuIxef zOkl0J*+Er;06Tc{L#D$O!piZ&x%)R>q6Zv(=3d6233o@rbKP0AMgJwGhtx1a$pvjc zdRGrwUmy}p)OppTgOl{n!;ZB|)Q8mJyY8eXM^R})%`i8ni9hTNSU&vLd;s%=u^LpT zS{K6G45o!2g`!bA?C@ngdT1Fnu?#Y9N~m@gnYe|(M|Um>MG!tB6}BJ--Tc0gl~PXd z?_RDHMsGJ1TNj4*=c&&i;L+0slP+xta|1J{gXWKlC=gWQ)pF=0r!&uZkZqR2YOaGC z+cap62fBg3Q^Hr{N!Fv zeuRY6FSP-U4Hj7*HQx!{67)8+0&W8=fV^Cd+O^Ch`q|oG&L(R)98=)=>}zkW>F~d= zzP9tpNH6wo+_;Yvs z>Uk>ixp5Rl@N|X1ruo@z!p!$@k$Zhs%?mGo9k`OX$s0Fa>$$<`{>@2#9T@pHLyo-`$EtoHV=HwJeFan`US24< zOoHOqa=TfQW#={P6NP1(_bu=e=pu$W##R(@1P43hBKS2pU3S~qom!5wNG5LhsV!w} z!Vz1B{Qg<_xg2V`B}Warn#J>JotUYa#ifi*Xvl{~OtRfG5Pxrt$;fxJwqakpN69^2 zLW=7U*>ZLFUkbG%r3XZ4lZoQ^F2 zLX*sSF~2`6IkQ68i&+n1%;Ygw@zXM+`qaSF!6UVPvO3qPf37^Qlr+y8?&?X%(vCA# zcHg+?2m>VOYD+q_FzM!(N0{TMNPX1ZKXUZ1jP$NgN zkm(DlMcS)C9}egAN(fpfdVe`!=(6YBzUZ>&3S7GI)~JH%qF{{^+7_)6NmFZ*nAA=~ zWET!k1+SKsZ^k5&inNVc$dt#HEmI2;M(CH+O{l-%qWEf7Q|*tE>?P+b2)jjX=Dq4- zYCvL|n`tHKbuWga%R3W$;l^QD4A99$tivnu1$yxUr(AVdIyuCkxp<02S`y4ki@JWW zH7Jdn%u_(1pWD(~`MU}0(taJU* zD%2qw8K>3?0>gy^>}`NO9b*eU;SyN+d_~(A7Pn(-SVpwA-@hFvUVpSgviwB5L!)r* zy8F^?BbP%zHx*}r%3T6!Yo^gXfET`efSiY%-VsgSgw&h&M7ZiuO~MvBw_FxZmRj%p z_2GGGD*Nk8I?A5x+|ACJE&94t*KfX!3K63gINl{bHTn=WO;^e35Ii`Bl7?49D>1*F z9e9ya_pxmz52=-0g}s9MvrG4k8ydPGyIOrphD@ad6AYSVLDgU6Q(ZrB4GDua!C6%a z59S18*SVR4jt-B9mN}E?t}Pi4m1Wa&@v~o^Mep&S4&luadW~0e4*IsWY?{{L92}(QcO9;~2-B zD+^5*kG++(HIiu#ZTuBYRZ(fho4yQlQjd$9*zPFR9Cs0ptZ{%j`y=PrN9=7-a$Udk z_=!y(co-3R6_(z?E7O06>WRm3C{46kk+Io_n$X4$zPcS(lp%@U8U6{GGyx$o`r+P- zZAW#oPc`INaE)@ZTf>mhbrXiD8vTCj#L9y!qe89Jy;x*XRx*_JS4^1ywTa~&D!+%N z5Q;I#*>ySo5_g0zmQ_l0Hz=`iC7Y}>InFWZYc&7gu6<0zg%orZDU&O1iPKqmEbA&J zT0IWPs#7Q%J3*G`i+Fc*)^WL#N9Mi#!?A55xc4+hHfQp9Hf*G?r0BGdf!lt1zxAC( zKgQ(twx26IDrrCS6b$p#{wCL zJzt(}^x!f28Ovg9bb){&6CMiOxqoTM_-TT}>?r@kxm_je;sJuk)T}YdAK2R2CWHoT zvy|9>!rWkcqe#QG$}#GJrdmzp`Et?pkS3L{HG8e!`7h&DIjx{#Zd5Pc8TIkiM$#A< zCyuD!X)R|VlQD~Z0Jb+aNZpXadcfWF^G(q&eqal#i{oR>WLrGTmk{r;z#=D4io)5q zwNu+Xu;N)#z9<+SWR~*z@lZPrPsc?Iv!oUJffJ#b^Mk^Ji=RKda(@0)#AwatIb(wy z!haI&v?H?0=()1MXF5DBQ@VE31~)0~SOYR)3Egu)uxQ z`(W%K9DGDz3YIXCnnKQ1ZJ3;PgmwP$uM}F}rMW%!pst#ke)`%q!%bT{*81>k_t~Ih zL#c&k#TcWGjI$_cRQEJlf;y#ug?%SK`TPA2&G}5(Q?_@VidhZyBW_SnUEF6Pp$`{y z!JZyJbUCc}ED0j-TlJv?pT*n49$e~?!!k_Vj8z#MWn7+vfVSNXn`yS7NfSP9hRK%{ z@H8btv86>S6*#@;nNU#VXVtzzefGucNjFm4hm^yAmVbU+JYG8o+G6o(KAZ0(r(h`& zPigT}gOgz+&}(5t8)iW|V{SKIqsp{r19z3VQ|RAT@Ga}2vRN!&g7kDZR#ajkUTc_o zlh{JL02}`!H~REZhxySD}`+`0$Y3Y?UFh- zNYNhBa^)eFD=@fzsg8terEi0X-V0}O#i+^V&{jzQVgMl>4HP< zA7Hjcq_MK`DH0Kt!#U`jNl0EZUY#-+`7qZk;0|>9=LPg^#_KmWLi^I@h zm*bqL?DlE8BWpz7pE*gO^5QZJ4A+v*My@VZI7Iz+c|uFox5$HD!H!~I zYP75ui5q`DzHYoQ#pVj8jHdJUD{nO5P~B5tx7J4gY27>z)x$ zs*A{@H3%Rxi%>s+JDEZem7{D_mq+c}R_-yq8Z}MSK}yLeJ#kZ2@lUi$!m=@!8vd#w zfc`BU9^pnpzE_;T6GvS8A|keW&Dqnhf#KP`@zOgOsI@D5qqY!jaf-M+_Eg4??d!7L zv`$=6Gb%x~qHcT`WCW6~ywS@|C`*{d5g*5BIQm!-4PV#vp2!$y5GPoKJa5E^Uo)9; zHbWs7b)D2mTu8JAM@MZ_dU5L^k_6)0t^q(o$_4fYxl+a6NRS!}{c%dAhN&n^(sDG2 zRMj|4*q2>gYclL=)CraB#2Hvtk*|-BJRP-|Djx~|2CQ=*UbT8sF-SFnucU*i`*Lu@v(Y>{->`E4TV6aI=9_5QijkrP@=>$ zWt|Dc6o2Mnwn1NdA~nytfI_=S{gk_pvEB#EoB$++6(^MaYf|%f2N?|8h7nRn$x{)F zlG%Itx>)y-Pu|Oi_W%9$sL>{VoQsJ~AtQu%Ttrr$)M1yKB?*!ZW^4L8NsiU-BK(tW zWmsJC?c|;c^4Wk=1uRQ`i@m#H?Fo;*6v~KIN$J9)J3}_=WbDQN{X_(sfo<(H=f^%e z$B)RZhUTtAGo6hZc@nKbJUeuMxcF;lNC=7M(L<))+^F|)h5aGhV!Qg4Q>u}l>2R4B zHo5cn`khUMuNZQX@2fmPnBlt#;xFxsi@xkA9VXK;R=!5UHM>qZ0LA;tl9)5H7kNz8 zAmdbq=P0pySh<E+0*#dKgr4!B`)2`z z`|+X%)@z@Ykvts->1kcF>`t?Q*vzT(Fq8d#DSu;QnfKW$u53}h@mWt4gTR*Fw6%wR z_v9{%YYPRv|CkK7F?NC{S!}S$0*TY+7+q?jhr`>hEQu|NeNwiGGt_ptnb+9gzLqM8 zj*gCH9#3shVzNQ3`rc@cs~;6^(Bl$kG-b^}7Kf+$(M(cPNkSy8L+00d!lF#lTz+`K z&=Fms-^g^{sQpDtkcswGFzDAcbR_dxw<%o-?u*cwKbyR{a|3Zf#1m|+&=Cu=Mgtoe zGL80IFzKKu-~^%;?w@9r&q&;-eVqCz@v#4M#d=~#4hD}fw?I4x8$I(Cl{%e0Nh83; z&@^tmUG&9`iSs&%8;kb0@Bp(;@*AGSS*ohL(2!`&^`r z!OTWAg`!aui4B)43k;I1CQXVR{(xwV~(cpnV;y<(=WambS@5fxB-P)= zX!%@bu-j0vfKWu@xfBa?yxa&~ywpcn9WjA=Kd5QC|nd(zqB{xGskOrKa0W z%765D_KH~g(7&F&wvFgUtfy>c1j9fx{k(m`D**L^L4``exVgF~a7YTLsv`KsoT*hh zUxa<96wnd@1;B3MT>x6yJ?FWjS2dF9=m9%Rde5X|b`Y98E({wE|u8Mo~id*WsyMf-riK?^~*qsC1 zQ~iBq6^loYU}A0pcz-Q48`J!KiB74>t>4sxhZeGBMI>6o8iG*;7Y{fSA*${i{HuxG zH2A0?311?#17@s+5$AGd6z)dIPYs?^*JejBQla7&hYhr^KB2YQXAj-saQ2q7*ix<1 zJAemFIELbG_DiAo9Z|JeZUrgPnjiME z!bNHBkp!?ZR2sqz!*2&p3Vevcv_aEcZ|So!HDP{H%PuoVkJtodeK* zPVk{qS$`T51Hsn#>0dOMXC(Cw)(f}3wDddR4Bqhl#0o1a$Uh=s18S&ne^~Gz$nl&v zXE+WmgD=l7);Usm)|kOj`h@9iSHB8U+SPwo?A|wy{gBo=$||O-x+{x)b)-OQ5`7m_ zc8qVto$FD=`Nt@bLkhJf7nN?H22$2P2P>xgTfc`;kKO%G0756d*rjl!-=D$Nt=8^r z7-%6ymP&X|JF*H_w3Evje|F1U^;gBYsH*76MV6NG=*K?2o4cKxh0cE-kIwE7V~Egp zes@gimMdosqf zWT~S6iUVlkUFb@LKx1qfk9n;fDz#r6S(Wo2<_yBpH2*Qih+N~R{E^7~AWaWKiOo!< zeFrklwJ3ba(%i^vI7y2riaJ~Yk=s51Y3--MWzls5^fiJN zFsg>ih9&1Wd&^ypx}d9RRdR|?EJ9b)(Nvb+_636q=9FCoGRZl*`w4Dxr%my};lMEC z`446X&_frxM?GOp78pgYtOERRr8}d#8EmX-$CujyfnKEcqc7xt*mj(UR2kp|A$D5s zLb&;D4`rxYCDKh(;kT7Eg*bAunk7;9iq?>5*wu9$BDlVW zNsh(*TxQ>yX%xPlYnahIiW43pKw`xnLiF?qISgeQeC9ibyv##T9)O&!xva}dyO=O- z5RA^7HfDA(4g2STVz`Y4{+Ox{ssV*dYadnKcO)W_>#stlq1tgasJ^~GYTdhzTIUY| zFdE()B^TH*C0`Vftx|vejn{<~%UhFiD?EdiUiXeE9JtAmG(pB#3U>8|LNo4ORGhq_3Ln1j((kch^M zI3Jc5*Rp&C`s+Q52Y_DzcX%PpVrywZ&`DKJCRczqkPPM>XL{1AF`9l<<>sbZC6pSLO(IChA_`%mH7Lw+>N#Zfbs7J9te!StoFBd%=4;-9u@*+Yv@2a=#)y!YE+O32kDw4KMN2K5ha33DknRn_((4p>ZU#;yT-A~`AHRfByNl+~g3 zfHKn^8w-K*`+T(c3Znxlr(w6YNhw0f;%CTCEwrm281Tkeu@;LhIxBC~O11PO2bpp$`h zW}#o)kSl7S=y#aBt6%%|C*V$fuo!9dLuBsl(q%hi7s5Ev0+3UcU%7EEfVEHGclWg{ zs^5>yf3MjqNabP4L}DsxfUqLypG7LV;eHoBK;n;E!4WK2-Q7LCg(idGb#9VcXLqtB z%ZOf41Uz*+;|dLlP?j71X?tHq>7S=C%hn&o;u!7oP|HLYTuIsnSo|+~8WK=qN!X;? zw@^>=I&wm>zvgN!+)%4FZNoeCXth`Hz{&Tojx|mkFwLCBvlpCZ0*`q}Ex^Zb@-QJ< zFnK1YiyilvSKb6@B}*yW>#U4cHgXqK1de7BB>HFcUyzbuqWo9CfaLOBnW&dM^xjct z)>EZ1tR`{S%xVNr)vNxg^Zw5`D1#ww=w{9U&b)}(k^y=!`T5Kuf^}z%JUtJuBL(fps?r{ z@|k!Ynn_w$ylQvXl5byr(fgW=3#nE0=Vm$X&L;A{0o5PumXohY88VsNxA-t=x*o7i z2d=yPG;FuOl|+cj^EEcObP~j%nrD~t2V)wt9<=2tHC(%bVFa-5E1*-c-m(j-*QqLp z`-oPEzzH-ymHY0U^9+Wvk82ihO682;*2IoKJrnRkUxQG+LC1XNw&H_!FW_;NWqd;j zexLVSD`jmskPjFPM?H}Tqjnyjh(d6fzVWT8XXI!s8O0I~GBf-_WQ0K36nW%K`7uF$ z>d2R&%2k4^Ix%MwXt=EnIV~Ie0})xh)eyo(f>H}9nskpD29(>|%8}v_Cse|%tFQkr zQ?QIme$xCns^&qkLY&!h!wYOwWIHFqPnrR)*X>~LZxsrrOjoKrR1|<@)D-62ZvJ@_ zh04o)BFWO^p2|pLu;ktQ3PCH1;>8XhBV@OVcTO8`5rxyGre7s$_7NT;y)k16By4>Y6=1(_2wcbZ>2fAQr&Q&MnC#?qDw& z5#!HbWS6dJX^PtlW4&V3k&EPY{kT2lKUzmz)`!V*)jmNQHD*|J`JYKaos~*>vU;BwpbcC{={I6U!-oxJbWXnLhSG zSI8#&TH`iJrmiZCWg+^9zJWVD{&N5v=^%YK3mqQI3`^+TF3B4LtvLg?p-+TC&=sCy zwddzZ=O-pyLTceGYmg2p2-|VxV%;~Yy=S9&{I}cCYTU)FJ@UC#i$Fr&r+xnJwFaAk zR+27gBgbMC`~pe@Ww7tPSq<*I0lN8v+9I$^F?==Z)M31ch*jh-wS)B_ngc*T_gZKrBBiXFVD(jiPiG4~E`6)Zo zg8!>F#9nl&RH7qSU?3F+m=zgY#NjFJ8NRpWbZ@anKL|Dv8jZk1Bqo;GM{^&VgK9SYWX+^6`{--Z_{{RsyN9yK8Hv~@9fk7qU5U%ehXb{E3AHbe76pCYd7V8(=BFg| zlfkpb2DE0dk_@SUB-Bu_O6gi=Uz4L*giov|w0ZY2g(*#uZ(D6?KYIy2-2(DTl?4_R z8@rIuBHXT_K+cePUWLcDDEhfLazvCJG#D}=gtuD}@kk8%>&7P)nhGoPmE*nZ>M7kR ziMY!{Tt?^i51wovyb99#+CvvAQ(8{q1g|*=>%#n8W1dFQ_I!BSlCLV-7(ey63^w=h zxuT4=@VGv9-yqevfKE@(5)_&urws6gJ*A66umi8D{dL4z(g@Q_>hyp*BlAYqujCI( zko>>fUNLKrMxy&@nT7c7AS_+(u#*^WjWLq-B@6^_Bre3u&RnHW_o#iiYF zcQ5Ryf0vN$+u@t1ZkqC$%UIE-a&$EPtoD*wQ)zgdcX`H($Kgo==Tz9hd5vg6w~JJb zM8-?U;(TLH{s?CyI3TVlNN@E%Wow^%hT>0Suvga)ZnB-58h){ZSs2qqZi4pCMdHML z(b(5FQc|_u4}2X2+Np5>I6kVp*97S+6J-Sx={t|dY#3lO}SCd5A29jbIh ze4LHh_rWj`GdNF05kIE^MxKCd@44R%noITn1Ddc`Az7Px@CS@?ZAfJMEi)&J@*=Ekf>)?46Y#~=|P4XT+@ zWShCSAa7VW+8wT}a9DO*x)`7qYPdDhHC)xDOXeLYEHO|vHjov?7yb4z@_|eDa|v7_{vWsC#W2Ly_Z$}oH&VGBr92Vly?aC= zo-YkVm>c9W(4WQ!snbtE4BJ-;>%Z0VKkxkMFJxE*!zVX6dfs>I{bhQub^*`}2e1l3 zSKe*EZLpe#6)RWnH)XY9>{qW|e}MN5(em$JY^UrY$A2Dga9_emusb7v zi!1vN%Hv(r*^&(u!67Y<33niC^gKxqUK<%A)1O#KgVu zQ;p8h%$(u5_XL!H#^z9y&B? z&=M|`b(VV_oo7siU;{{-6|K5wDci8Ws`4D+pzA>sWrK~0e+J3BHM#+59182;+6glD zqA12|2+I|2IU+t6kB}>v(XV7O9!)rcmveSmv67P3!ZlOl3kZ*#u0Hu`kzFu9lI(p2U=Fcj;TptU+E!{^2YBZEV<|GJxd`i`unKP;Ae+8Xs5#izw|d%a#_}H*~7(2dt-)-U7g4SAhbK=sJX+Y`|9ceI1BhJO1Pq8@@bK zmns5N0uB?nlPJo6Uq4@F$r~Wj)_AG&wP%M&0JFMfOxk3AG)Mm~z1t&+s8wJ9@M(yB z;6cw%>c=L}&>=^;Y&6DPz@DY7r;pnV^(Zq{$7FLY2vj{y1m%cPzHR?PA@<8EUQ1BZ zJGTOf=c$@>p!|2Y?9kYKVKBaEBsipNH8 z)V0Vs_MtZ4CLYdc zPOTOpX;S05^*Jb#R;7m^*SjM-Z7;lcXQ0wYprh?>#WEdyIs!2~j9ik_01cIkW3f7< zjpN;+mdkz1+75K8OO3dUzc-$2l)q`32}c%Iz>@3stptKr|k-B3R5%7h9*o7J>eVy!2T+LIZVl0QYxt2J59xPUNS@Ue}(K zOu>b48L9A2IGK1gs8(lt*Ad@dh(RF#Fjx%kc2m+7SCDVQFO!Q91*4IU*F*)bO0**r zbq*Ij+q%~dctqEslWm<9s#i99iDhOvU|g8?!f>|oazc*D(g%wfDr z(LAl%0sA;_3E;31DlARhwh>lSB?YlOu4cvWNFGa)-ET>f#r=0vS$Kz}(?Y`Ujhd(y z&=G=GAH~7uwbmdw>v6L4ZI3G%`Q7<(t#PI&%2m_Bq-9bl2*!9-uoe$~C8kaJi;I>& zf+OrEeDMqx@n(Fyx1W5?iChc;BwL9@dQow4Kxyo49NU%yYMIL3iztm*Tggzsk;XFp zG0i5g3}{khm*pq$7z?$HYb~h#GN&%JcGQ3BemyOmaAF?~!(fY|3rhggtYfJMp}ui~ zK*1bh$^2^{P?>kIZBe!h|ARXaz*CEC4z1WtP{<75CJej)&Yg!!%%xQ`Cr(bl3|cwi ziSCYVk45wLJ`~4cpgie&Ts=|1UO(tl<5kXbeu_6eK9Um>E(RRnT&&BCM|nXP0lAEo#Fm{#a z-xYtMAj%cTB{xsH8k8khJYV|1&BY_#R+Q-?e5v;xNs5_lge}^ZP{yKa&ecf&tl7!! z=(erSIN-F5E(#Y6;J$jw=&9Nw#p$~JsJgdb&t1seQQ)vM9=(Y}`G}?IvEUN~7cE1! zHwl-NS8t*Da#fR^HuigDT^`1APAO+vfO!FIGlwP%;7J%5s`=|`-18pUvKx>dMxv64 zqO^6t?8#F{@mvAHADvp)zSH!n&jh(ejmL0Xpbh!lRnMp&U$*#0!28;qIS@*ztT9*i z2Og-mEQEZuR5UZFyY4Z%_`$9ro$ok{!cAS5?k5K#^)x1xo{LHhc3#Jvt*%?w5&&g$ zQ(m!gTx>tbvkvL6gR?|HW;3~RR z{A_5!8y4W-V&s=w&(PbIrb~M*Y|_C-F|emy!o))(4lq%rNG?^2 zK1=4jGCz(05TQ?tURZb~V{(C_Wm$V?h$2Sxiava|{BZB-|L2kVUt^b`NEsQN6y4;* zlE*pNVZn0Ub+#ezj?0^P+x2Fw)>0I(KX`>K%~G&TakiKWq2>i0>a}kk`>eKg!ep-& zd)=m%bSF8du?2$>SpTn2Imflt69{m6N zHL4;B2V6ARIA7fC@*0pY=B#G{xrVO8M4TIm9cZhZqR0*2LU6*JXRq2 z>SWOtoJrohYNNK_#Mk+F-6lsjy0Fp5Dg(FHPL^&A`qtogg43xIcC5w3Q2Ly!Vcht= z%BH>#H*n&igw^3(Kzvebdi^~~I!9@cUYV3;8}Z;2BbKvJxQrARkq$rlzU2H~{?Zi% zHcPoEJ|_BFB`FmY%%=!z?)o{qr*=)L5CgV#*7+hzl*F?GuNH#>UgKBj41hDOJh*>? z{0$)pc9UZhO+9v^wT6R!?RcoVNAC2RbK5l(-%f+zCRIMhnKZa9mm0(rhrK=n! z;O79uuZ<0teRWYR;Sl<|L|EfTeCQ#6=%+}mw;j`osItO4ce;f=`R8I(7~G5=0HYOj zRs$7nezX!yMGh_Ww(B5FvEEY)U~LOJl$S$U--3y{N)@xC~7Z>1-$a>hYw zPmN3k+EceHMC{S8=eDpsO@J5Z-Me&|anMUNGK3fFli0QowrIZn(~LN-&?AWWo;lEK zFCkG5m*7+@cy(paq=n}+IoCm5&k@qM+JvZWXSQ)@mf@w_XO9BWs4mhCb(N*mCq~i- zLk~bQBIYBq!2qwnOK%mb77JVCi8dzA^BUKN6O%-v>9}>6PhtSR5R-6BvYQEa@t1L% zxGt>A_N*sCfX2{sD|MF9Wj`D3_u zU24Ebx4)7qnldk4Ya97O?osZlKW)BsaiPx(_bo@)g7f^b05HRP)pJS4bN~Wb3mxme z_A@tNF1YfEMvW`-CRhHx9qHRdjBkOyER246Ob;t|r|AYaGsrksayU|`+5PXFRxe~< zE^{*^{Z!_G00+)B3NXjt;NI~=M5C_U!3(3PpOvTl&)X5>l%O5oP>7CH4y_--W_X=L z${%XfXI&iJ=wLJnOUn|!iH^OpO<$+H};9<#{9^7|5(SHx*bpTGXOJHt)EdcxcX$j+prt6oqg_GxzJM*QB;1D!4pDSp|4LIUi9+lIYa$mUi z;h=^(*6{)np*NI#6mx2Nq9P1fGpq*>4_U+~yKhZk(~Eb2o~(Z9Tl}f~4a*v5M3T<) zR#-+U79a-xI;g1)|75YQV<50e{qtdueH_x^@6wiK4H#4{IwFsMHADw?$R1lH2Ed$L zuv3Rrx2bUYvJ!u|#v7F^@ksgk{~h86+ARLVO>Uo_7>qw9vVQN0r7W{z-0B0PaAB<9 zrPOFF;v_gKf~=vkz7kr4FApp+>9l>vb<7h%8C7?1G+#!(kDKL6AA}nu@K<7&KC0C@@FkGln%q)g^ zSp<1_giQa&B8)xdwlG3vu7U1^}fri4M3 z7{XHy&6*R|DPEGu<^^|oZ63Jasi2(kf2e_2{^*Mi>+*DAfPBwKeYjyoN5z~gbQSv< zc>}#SkVw0$q?f!~I5AvlkSat5d$}BiSvHnH0p41EiDJYi$X@WUN><}M3$}h+I(}DmhJ&eV z-LD>5wLyBis^&O2Im2_f+gq2Hec>}U(UmwC@Tjv}kNK&rP37601>Z0! zljX(w(3cIKB)B7Q(H$lfqc%*2g6@i&h^KWO7nT10T3^peZV7-EK@}HBsQ{WC@b5mGPfQ~?qI^syCeugUB*nEad|tj(-ffwMc3-tp8l=|*1I?AxtXKbnDP4^@Tf~c*UnaxSjzmJ zM!iQtJud%q-hmM&GM!9uBlI~79#slVrR$Oh6cWjjV$}q8^BB5!vv^f}s!j2WhifMlePD_b2j;Wg-)}#HGBAHj={8K;- zEUxYd&3P#4uZVp^mjVAkw7FCL2ctOgJ6bbgNu;&S1Gdi*&+36P`);-Z*>`vU`T~Ad zfJClGk2&d-YBKGIp2Croz-6;GtD~EQp$>?Ha@HebXhe#Nq!5+fF5MEgFcpBvA@Si7 zv!lLob{Dm3NuZpDt;X(dzRYgm*8-TKy&N%>0h5;DifV-Q`qoP+LQK_=>YaT0kuX<; z_)LNQ{u@)%8Mtx)-losBJIgrKF^EOQo8^%Ub!WOB++1!WlcGQ8$(R*6uF(a@%8^os zKuFE$kdC0HKF-`P8*@76rfLv4a0bPKmw}xqwm}g>?joN%o|M4~XN;IuTpn{KK^ zf~tclgi1`P*;jR?4Z!4N$W`+dlmr(ed873lv+eV<4u96`&K^pbXU+!E9YeVGc`$I z7s*;A;5Kh1%6+-u_~8ikXXK8vvh)a9*f5o~ZF#6j7by2gVzMWisK0Ua`V$X1w-Kzh zfH(q^*lG)(^uIPwmbUU*b+a15s?UgzYc=PZ>R0c0&U46~YhCgJmzF?ADU0k@{}Z)p*~Lia+=(WS(|y=`f9#~@{KLcavbL{y0#v`i0w!UA znnFt_w0@Jl(N~5CjdXq2?ZRnOK(OTf>C}^IQjiy`tt;ZOzzv)+6&^)XICn3|vg{yq z*aA`gK$?Vjs+xE~$b89}a4Cv1g~{w2`Yb{2=3twC|3D*_?gVBVYnJbibO9Q_Df#mQ zfXb7z;bVX99j04iapusn{tHsL=E-^xdbEZ(R)-Jn4^J0RkXB&=|eQ_%)&=Cr6#*-hgP|tHG41>ucqclDn3Qre497(P literal 0 HcmV?d00001 diff --git a/features/wallet/impl/src/main/res/drawable/ill_crypto_org_card3_120_106.webp b/features/wallet/impl/src/main/res/drawable/ill_crypto_org_card3_120_106.webp new file mode 100644 index 0000000000000000000000000000000000000000..ec14a4dcb55d6f59780920d3eb528d91b7c53d87 GIT binary patch literal 32680 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 literal 0 HcmV?d00001 diff --git a/features/wallet/impl/src/main/res/drawable/ill_stealth_cards2_120_106.webp b/features/wallet/impl/src/main/res/drawable/ill_stealth_cards2_120_106.webp new file mode 100644 index 0000000000000000000000000000000000000000..5a39ebf1ab8819953142fd99fb64bb5b8b63befa GIT binary patch literal 10750 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 literal 0 HcmV?d00001 diff --git a/features/wallet/impl/src/main/res/drawable/ill_stealth_cards3_120_106.webp b/features/wallet/impl/src/main/res/drawable/ill_stealth_cards3_120_106.webp new file mode 100644 index 0000000000000000000000000000000000000000..0e6b50a2fede1d2c70f8d3e2e6bc672cd4cde60c GIT binary patch literal 15840 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 Date: Tue, 29 Oct 2024 09:18:22 +0200 Subject: [PATCH 038/106] Updated on 2026-08-14 --- .../state/helpers/StakingBalanceUpdater.kt | 14 ++++++++++++-- .../state/helpers/StakingTransactionSender.kt | 2 +- .../presentation/viewmodel/StakingViewModel.kt | 9 +++++++-- 3 files changed, 20 insertions(+), 5 deletions(-) diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/helpers/StakingBalanceUpdater.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/helpers/StakingBalanceUpdater.kt index aa8342846e..e10d32b8a1 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/helpers/StakingBalanceUpdater.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/helpers/StakingBalanceUpdater.kt @@ -29,7 +29,7 @@ internal class StakingBalanceUpdater @AssistedInject constructor( @Assisted private val cryptoCurrencyStatus: CryptoCurrencyStatus, @Assisted private val yield: Yield, ) { - fun scheduleUpdates() { + fun fullUpdate() { coroutineScope.launch { listOf( // we should update network to find pending tx after 1 sec @@ -56,7 +56,7 @@ internal class StakingBalanceUpdater @AssistedInject constructor( } } - suspend fun instantUpdate() { + suspend fun partialUpdate() { coroutineScope { listOf( async { @@ -72,6 +72,16 @@ internal class StakingBalanceUpdater @AssistedInject constructor( } } + suspend fun initialUpdate() { + coroutineScope { + listOf( + async { + updateStakeBalance() + }, + ).awaitAll() + } + } + private suspend fun updateNetworkStatuses(delay: Long = BALANCE_UPDATE_DELAY) { updateDelayedNetworkStatusUseCase( userWalletId = userWallet.walletId, diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/helpers/StakingTransactionSender.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/helpers/StakingTransactionSender.kt index 5e5538c4e1..e61018217b 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/helpers/StakingTransactionSender.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/helpers/StakingTransactionSender.kt @@ -227,7 +227,7 @@ internal class StakingTransactionSender @AssistedInject constructor( networkId = cryptoCurrencyStatus.currency.network.id, ).getOrElse { "" } - balanceUpdater.scheduleUpdates() + balanceUpdater.fullUpdate() onSendSuccess(txUrl) }, ) diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/viewmodel/StakingViewModel.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/viewmodel/StakingViewModel.kt index 6850137ee4..68fef18387 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/viewmodel/StakingViewModel.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/viewmodel/StakingViewModel.kt @@ -195,6 +195,7 @@ internal class StakingViewModel @Inject constructor( private var approvalJobHolder: JobHolder = JobHolder() private var feeJobHolder: JobHolder = JobHolder() private var sendTransactionJobHolder = JobHolder() + private var stepChangesJobHolder = JobHolder() init { subscribeOnSelectedAppCurrency() @@ -207,6 +208,7 @@ internal class StakingViewModel @Inject constructor( approvalJobHolder.cancel() feeJobHolder.cancel() sendTransactionJobHolder.cancel() + stepChangesJobHolder.cancel() } override fun onBackClick() { @@ -330,7 +332,7 @@ internal class StakingViewModel @Inject constructor( override fun onRefreshSwipe(isRefreshing: Boolean) { stateController.update(SetInitialLoadingStateTransformer(isRefreshing)) coroutineScope.launch { - balanceUpdater.instantUpdate() + balanceUpdater.partialUpdate() }.invokeOnCompletion { stateController.update(SetInitialLoadingStateTransformer(false)) } @@ -762,6 +764,8 @@ internal class StakingViewModel @Inject constructor( .onEach { maybeStatus -> maybeStatus.fold( ifRight = { status -> + if (status.value !is CryptoCurrencyStatus.Loaded) return@fold + if (!isInitialInfoAnalyticSent) { isInitialInfoAnalyticSent = true val balances = status.value.yieldBalance as? YieldBalance.Data @@ -822,7 +826,7 @@ internal class StakingViewModel @Inject constructor( when { isInitState() -> { updateInitialData() - onRefreshSwipe(isRefreshing = false) + balanceUpdater.initialUpdate() } isAssentState() -> { getFee() @@ -839,6 +843,7 @@ internal class StakingViewModel @Inject constructor( } .flowOn(dispatchers.main) .launchIn(viewModelScope) + .saveIn(stepChangesJobHolder) } private fun subscribeOnActionsUpdates() { From 5f3a5989bc996edd41ce7335f47741decec30bc7 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 28 Oct 2024 19:03:16 +0500 Subject: [PATCH 039/106] Updated on 2026-08-14 --- core/res/src/main/res/values-ru/strings.xml | 3 + core/res/src/main/res/values/strings.xml | 3 + .../presentation/state/StakingNotification.kt | 6 +- .../AddStakingNotificationsTransformer.kt | 126 ++------------- ...issStakingNotificationsStateTransformer.kt | 2 +- .../StakingInfoNotificationsFactory.kt | 152 ++++++++++++++++++ .../viewmodel/StakingViewModel.kt | 2 + 7 files changed, 180 insertions(+), 114 deletions(-) rename features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/{ => notifications}/AddStakingNotificationsTransformer.kt (67%) rename features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/{ => notifications}/DismissStakingNotificationsStateTransformer.kt (98%) create mode 100644 features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/notifications/StakingInfoNotificationsFactory.kt diff --git a/core/res/src/main/res/values-ru/strings.xml b/core/res/src/main/res/values-ru/strings.xml index 99a8ff0869..884e4e6cc6 100644 --- a/core/res/src/main/res/values-ru/strings.xml +++ b/core/res/src/main/res/values-ru/strings.xml @@ -566,6 +566,8 @@ Восстановление кода доступа Идентичные карты Код доступа + Оплата с + Через Группы По балансу Упорядочить токены @@ -758,6 +760,7 @@ Стейкинг в сети %1$s с новым валидатором автоматически переведет ваши текущие застейканные средства на него. Реинвестируйте свои заработанные награды в вашу застейканную сумму, увеличивая потенциальный доход Рестейк позволяет вам переместить средства из одного валидатора в другого без необходимости выхода из стейкинга. + Вы собираетесь застейкать весь баланс, рекомендуем оставить небольшую сумму для оплаты комиссии сети при выходе из стейкинга или получении награды. Разблокируйте свои средства, чтобы вывести их из стейкинга. Разблокировка займёт %s. Ваши средства будут доступны для использования после 21-дневного периода отзыва. Награда будет выведена вместе с вашими выводими средствами. Ваши средства будут доступны после %s периода отзыва. diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml index 9b1c743f98..486f321553 100644 --- a/core/res/src/main/res/values/strings.xml +++ b/core/res/src/main/res/values/strings.xml @@ -551,9 +551,11 @@ Access code restore Identical cards Access code + Pay with Our services are not available in this country Change or confirm it Your residence has been identified as + Via Group By balance Organize tokens @@ -742,6 +744,7 @@ Staking in the %1$s network with a new validator will automatically transfer all previously staked funds to this validator Reinvests your earned rewards in your staked amount, increasing potential earnings. Restake lets you move your funds from one validator to another without the need to unstake + You’re about to stake your entire balance. We recommend leaving a small amount to cover network fees for unstaking or claiming rewards. Unlock your money to withdraw it from staking process. Unlocking takes %s. Your funds will be available for use after the 21-day unbonding period. Reward will be withdrawn along with your unstaking funds. Your funds will be available for use after the %s unbonding period. diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/StakingNotification.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/StakingNotification.kt index 8472172758..5b02449fa4 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/StakingNotification.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/StakingNotification.kt @@ -59,7 +59,6 @@ internal object StakingNotification { subtitle = subtitle, buttonsState = buttonsState, onCloseClick = onCloseClick, - ) { data class EarnRewards( val subtitleText: TextReference, @@ -68,6 +67,11 @@ internal object StakingNotification { subtitle = subtitleText, ) + data object StakeEntireBalance : StakingNotification.Info( + title = resourceReference(R.string.common_network_fee_title), + subtitle = resourceReference(R.string.staking_notification_stake_entire_balance_text), + ) + data class Unstake( val cooldownPeriodDays: Int, @StringRes val subtitleRes: Int, diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/AddStakingNotificationsTransformer.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/notifications/AddStakingNotificationsTransformer.kt similarity index 67% rename from features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/AddStakingNotificationsTransformer.kt rename to features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/notifications/AddStakingNotificationsTransformer.kt index 5c3bfbd6e0..2f086ac68c 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/AddStakingNotificationsTransformer.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/notifications/AddStakingNotificationsTransformer.kt @@ -1,4 +1,4 @@ -package com.tangem.features.staking.impl.presentation.state.transformers +package com.tangem.features.staking.impl.presentation.state.transformers.notifications import com.tangem.common.ui.amountScreen.models.AmountState import com.tangem.common.ui.notifications.NotificationUM @@ -11,21 +11,14 @@ import com.tangem.common.ui.notifications.NotificationsFactory.addReserveAmountE import com.tangem.common.ui.notifications.NotificationsFactory.addTransactionLimitErrorNotification import com.tangem.common.ui.notifications.NotificationsFactory.addValidateTransactionNotifications import com.tangem.core.ui.extensions.networkIconResId -import com.tangem.core.ui.extensions.pluralReference -import com.tangem.core.ui.extensions.resourceReference -import com.tangem.core.ui.extensions.wrappedList import com.tangem.domain.appcurrency.model.AppCurrency -import com.tangem.domain.staking.model.stakekit.BalanceType import com.tangem.domain.staking.model.stakekit.Yield -import com.tangem.domain.staking.model.stakekit.YieldBalance import com.tangem.domain.staking.model.stakekit.action.StakingActionCommonType -import com.tangem.domain.staking.model.stakekit.action.StakingActionType import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.domain.tokens.model.warnings.CryptoCurrencyCheck import com.tangem.domain.tokens.model.warnings.CryptoCurrencyWarning import com.tangem.domain.transaction.error.GetFeeError -import com.tangem.features.staking.impl.R import com.tangem.features.staking.impl.presentation.state.FeeState import com.tangem.features.staking.impl.presentation.state.StakingNotification import com.tangem.features.staking.impl.presentation.state.StakingStates @@ -33,8 +26,6 @@ import com.tangem.features.staking.impl.presentation.state.StakingUiState import com.tangem.features.staking.impl.presentation.state.utils.checkAndCalculateSubtractedAmount import com.tangem.features.staking.impl.presentation.state.utils.checkFeeCoverage import com.tangem.lib.crypto.BlockchainUtils -import com.tangem.lib.crypto.BlockchainUtils.isCosmos -import com.tangem.lib.crypto.BlockchainUtils.isTron import com.tangem.utils.Provider import com.tangem.utils.extensions.orZero import com.tangem.utils.transformer.Transformer @@ -53,6 +44,13 @@ internal class AddStakingNotificationsTransformer( private val isSubtractAvailable: Boolean, private val yield: Yield, ) : Transformer { + + private val stakingInfoNotificationsFactory = StakingInfoNotificationsFactory( + cryptoCurrencyStatusProvider = cryptoCurrencyStatusProvider, + yield = yield, + isSubtractAvailable = isSubtractAvailable, + ) + override fun transform(prevState: StakingUiState): StakingUiState { val cryptoCurrencyStatus = cryptoCurrencyStatusProvider() val balance = cryptoCurrencyStatus.value.amount.orZero() @@ -105,7 +103,12 @@ internal class AddStakingNotificationsTransformer( isFeeCoverage = isFeeCoverage && isEnterAction && !sendingAmount.equals(minimumRequirement), ) - addInfoNotifications(prevState) + stakingInfoNotificationsFactory.addInfoNotifications( + notifications = this, + prevState = prevState, + sendingAmount = sendingAmount, + feeValue = feeValue, + ) }.toImmutableList() return prevState.copy( @@ -241,105 +244,4 @@ internal class AddStakingNotificationsTransformer( add(notification) } } - - private fun MutableList.addInfoNotifications(prevState: StakingUiState) { - when (prevState.actionType) { - StakingActionCommonType.Enter -> addEnterInfoNotifications() - StakingActionCommonType.Exit -> addExitInfoNotifications() - is StakingActionCommonType.Pending -> addPendingInfoNotifications(prevState) - } - } - - private fun MutableList.addExitInfoNotifications() { - val cooldownPeriodDays = yield.metadata.cooldownPeriod?.days - if (cooldownPeriodDays != null) { - add( - StakingNotification.Info.Unstake( - cooldownPeriodDays = cooldownPeriodDays, - subtitleRes = if (isCosmos(cryptoCurrencyStatusProvider().currency.network.id.value)) { - R.string.staking_notification_unstake_cosmos_text - } else { - R.string.staking_notification_unstake_text - }, - ), - ) - } - } - - private fun MutableList.addEnterInfoNotifications() { - addTronRevoteNotification() - } - - private fun MutableList.addPendingInfoNotifications(prevState: StakingUiState) { - val confirmationState = prevState.confirmationState as? StakingStates.ConfirmationState.Data - val pendingActionType = confirmationState?.pendingAction?.type - val (titleReference, textReference) = when (pendingActionType) { - StakingActionType.CLAIM_REWARDS -> { - resourceReference(R.string.common_claim) to - resourceReference(R.string.staking_notification_claim_rewards_text) - } - StakingActionType.RESTAKE_REWARDS -> { - resourceReference(R.string.staking_restake) to - resourceReference(R.string.staking_notification_restake_rewards_text) - } - StakingActionType.WITHDRAW -> { - resourceReference(R.string.staking_withdraw) to - resourceReference(R.string.staking_notification_withdraw_text) - } - StakingActionType.UNLOCK_LOCKED -> { - val cooldownPeriodDays = yield.metadata.cooldownPeriod?.days - if (cooldownPeriodDays != null) { - resourceReference(R.string.staking_unlocked_locked) to resourceReference( - R.string.staking_notification_unlock_text, - wrappedList( - pluralReference( - id = R.plurals.common_days, - count = cooldownPeriodDays, - formatArgs = wrappedList(cooldownPeriodDays), - ), - ), - ) - } else { - null to null - } - } - StakingActionType.VOTE_LOCKED -> { - resourceReference(R.string.staking_revote) to - resourceReference(R.string.staking_notifications_revote_tron_text) - } - StakingActionType.RESTAKE -> { - resourceReference(R.string.staking_restake) to - resourceReference(R.string.staking_notification_restake_text) - } - else -> null to null - } - - if (titleReference != null && textReference != null) { - add( - StakingNotification.Info.Ordinary( - title = titleReference, - text = textReference, - ), - ) - } - } - - private fun MutableList.addTronRevoteNotification() { - val cryptoCurrencyStatus = cryptoCurrencyStatusProvider() - val isTron = isTron(cryptoCurrencyStatus.currency.network.id.value) - val hasStakedBalance = (cryptoCurrencyStatus.value.yieldBalance as? YieldBalance.Data)?.balance - ?.items?.any { - it.type == BalanceType.PREPARING || - it.type == BalanceType.STAKED || - it.type == BalanceType.LOCKED - } == true - if (isTron && hasStakedBalance) { - add( - StakingNotification.Info.Ordinary( - title = resourceReference(R.string.staking_revote), - text = resourceReference(R.string.staking_notifications_revote_tron_text), - ), - ) - } - } } \ No newline at end of file diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/DismissStakingNotificationsStateTransformer.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/notifications/DismissStakingNotificationsStateTransformer.kt similarity index 98% rename from features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/DismissStakingNotificationsStateTransformer.kt rename to features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/notifications/DismissStakingNotificationsStateTransformer.kt index a5fc4d9bc5..ba9f953c0d 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/DismissStakingNotificationsStateTransformer.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/notifications/DismissStakingNotificationsStateTransformer.kt @@ -1,4 +1,4 @@ -package com.tangem.features.staking.impl.presentation.state.transformers +package com.tangem.features.staking.impl.presentation.state.transformers.notifications import com.tangem.common.ui.notifications.NotificationUM import com.tangem.features.staking.impl.presentation.state.StakingStates diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/notifications/StakingInfoNotificationsFactory.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/notifications/StakingInfoNotificationsFactory.kt new file mode 100644 index 0000000000..f610a6b17b --- /dev/null +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/notifications/StakingInfoNotificationsFactory.kt @@ -0,0 +1,152 @@ +package com.tangem.features.staking.impl.presentation.state.transformers.notifications + +import com.tangem.common.ui.notifications.NotificationUM +import com.tangem.core.ui.extensions.pluralReference +import com.tangem.core.ui.extensions.resourceReference +import com.tangem.core.ui.extensions.wrappedList +import com.tangem.domain.staking.model.stakekit.BalanceType +import com.tangem.domain.staking.model.stakekit.Yield +import com.tangem.domain.staking.model.stakekit.YieldBalance +import com.tangem.domain.staking.model.stakekit.action.StakingActionCommonType +import com.tangem.domain.staking.model.stakekit.action.StakingActionType +import com.tangem.domain.tokens.model.CryptoCurrencyStatus +import com.tangem.features.staking.impl.R +import com.tangem.features.staking.impl.presentation.state.StakingNotification +import com.tangem.features.staking.impl.presentation.state.StakingStates +import com.tangem.features.staking.impl.presentation.state.StakingUiState +import com.tangem.lib.crypto.BlockchainUtils.isCosmos +import com.tangem.lib.crypto.BlockchainUtils.isTron +import com.tangem.utils.Provider +import com.tangem.utils.extensions.orZero +import java.math.BigDecimal + +internal class StakingInfoNotificationsFactory( + private val cryptoCurrencyStatusProvider: Provider, + private val yield: Yield, + private val isSubtractAvailable: Boolean, +) { + + fun addInfoNotifications( + notifications: MutableList, + prevState: StakingUiState, + sendingAmount: BigDecimal, + feeValue: BigDecimal, + ) = with(notifications) { + when (prevState.actionType) { + StakingActionCommonType.Enter -> addEnterInfoNotifications(sendingAmount, feeValue) + StakingActionCommonType.Exit -> addExitInfoNotifications() + is StakingActionCommonType.Pending -> addPendingInfoNotifications(prevState) + } + } + + private fun MutableList.addExitInfoNotifications() { + val cooldownPeriodDays = yield.metadata.cooldownPeriod?.days + if (cooldownPeriodDays != null) { + add( + StakingNotification.Info.Unstake( + cooldownPeriodDays = cooldownPeriodDays, + subtitleRes = if (isCosmos(cryptoCurrencyStatusProvider().currency.network.id.value)) { + R.string.staking_notification_unstake_cosmos_text + } else { + R.string.staking_notification_unstake_text + }, + ), + ) + } + } + + private fun MutableList.addEnterInfoNotifications( + sendingAmount: BigDecimal, + feeValue: BigDecimal, + ) { + addTronRevoteNotification() + addStakingEntireBalanceNotification(sendingAmount, feeValue) + } + + private fun MutableList.addPendingInfoNotifications(prevState: StakingUiState) { + val confirmationState = prevState.confirmationState as? StakingStates.ConfirmationState.Data + val pendingActionType = confirmationState?.pendingAction?.type + val (titleReference, textReference) = when (pendingActionType) { + StakingActionType.CLAIM_REWARDS -> { + resourceReference(R.string.common_claim) to + resourceReference(R.string.staking_notification_claim_rewards_text) + } + StakingActionType.RESTAKE_REWARDS -> { + resourceReference(R.string.staking_restake) to + resourceReference(R.string.staking_notification_restake_rewards_text) + } + StakingActionType.WITHDRAW -> { + resourceReference(R.string.staking_withdraw) to + resourceReference(R.string.staking_notification_withdraw_text) + } + StakingActionType.UNLOCK_LOCKED -> { + val cooldownPeriodDays = yield.metadata.cooldownPeriod?.days + if (cooldownPeriodDays != null) { + resourceReference(R.string.staking_unlocked_locked) to resourceReference( + R.string.staking_notification_unlock_text, + wrappedList( + pluralReference( + id = R.plurals.common_days, + count = cooldownPeriodDays, + formatArgs = wrappedList(cooldownPeriodDays), + ), + ), + ) + } else { + null to null + } + } + StakingActionType.VOTE_LOCKED -> { + resourceReference(R.string.staking_revote) to + resourceReference(R.string.staking_notifications_revote_tron_text) + } + StakingActionType.RESTAKE -> { + resourceReference(R.string.staking_restake) to + resourceReference(R.string.staking_notification_restake_text) + } + else -> null to null + } + + if (titleReference != null && textReference != null) { + add( + StakingNotification.Info.Ordinary( + title = titleReference, + text = textReference, + ), + ) + } + } + + private fun MutableList.addTronRevoteNotification() { + val cryptoCurrencyStatus = cryptoCurrencyStatusProvider() + val isTron = isTron(cryptoCurrencyStatus.currency.network.id.value) + val hasStakedBalance = (cryptoCurrencyStatus.value.yieldBalance as? YieldBalance.Data)?.balance + ?.items?.any { + it.type == BalanceType.PREPARING || + it.type == BalanceType.STAKED || + it.type == BalanceType.LOCKED + } == true + if (isTron && hasStakedBalance) { + add( + StakingNotification.Info.Ordinary( + title = resourceReference(R.string.staking_revote), + text = resourceReference(R.string.staking_notifications_revote_tron_text), + ), + ) + } + } + + private fun MutableList.addStakingEntireBalanceNotification( + sendingAmount: BigDecimal, + feeValue: BigDecimal, + ) { + val cryptoCurrencyStatus = cryptoCurrencyStatusProvider() + val balance = cryptoCurrencyStatus.value.amount.orZero() + + val isEntireBalance = sendingAmount.plus(feeValue) == balance + + if (isEntireBalance && isSubtractAvailable) { + add(StakingNotification.Info.StakeEntireBalance) + } + } +} \ No newline at end of file diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/viewmodel/StakingViewModel.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/viewmodel/StakingViewModel.kt index 68fef18387..ae14a1fae3 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/viewmodel/StakingViewModel.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/viewmodel/StakingViewModel.kt @@ -61,6 +61,8 @@ import com.tangem.features.staking.impl.presentation.state.helpers.StakingTransa import com.tangem.features.staking.impl.presentation.state.transformers.* import com.tangem.features.staking.impl.presentation.state.transformers.amount.* import com.tangem.features.staking.impl.presentation.state.transformers.approval.* +import com.tangem.features.staking.impl.presentation.state.transformers.notifications.AddStakingNotificationsTransformer +import com.tangem.features.staking.impl.presentation.state.transformers.notifications.DismissStakingNotificationsStateTransformer import com.tangem.features.staking.impl.presentation.state.transformers.validator.ValidatorSelectChangeTransformer import com.tangem.features.staking.impl.presentation.state.utils.isSingleAction import com.tangem.features.staking.impl.presentation.state.utils.withStubUnstakeAction From 47f5ac82df354a3bbebf4b876bd378af4f66b6e1 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 28 Oct 2024 17:27:49 +0400 Subject: [PATCH 040/106] Updated on 2026-08-14 --- .../tap/di/domain/TokensDomainModule.kt | 11 --- .../repository/DefaultCurrenciesRepository.kt | 34 +++++--- .../domain/tokens/GetNodlTokenListUseCase.kt | 79 ------------------- .../CurrenciesStatusesOperations.kt | 50 ------------ .../tokens/operations/TokenListOperations.kt | 26 ------ .../SingleWalletWithTokenContentLoader.kt | 6 +- ...ngleWalletWithTokenContentLoaderFactory.kt | 6 +- .../SingleWalletWithTokenListSubscriber.kt | 16 ++-- 8 files changed, 34 insertions(+), 194 deletions(-) delete mode 100644 domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetNodlTokenListUseCase.kt diff --git a/app/src/main/java/com/tangem/tap/di/domain/TokensDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/TokensDomainModule.kt index c28ded59d7..c72518ff14 100644 --- a/app/src/main/java/com/tangem/tap/di/domain/TokensDomainModule.kt +++ b/app/src/main/java/com/tangem/tap/di/domain/TokensDomainModule.kt @@ -67,17 +67,6 @@ internal object TokensDomainModule { ) } - @Provides - @Singleton - fun provideGetCardTokensListUseCase( - currenciesRepository: CurrenciesRepository, - quotesRepository: QuotesRepository, - networksRepository: NetworksRepository, - stakingRepository: StakingRepository, - ): GetNodlTokenListUseCase { - return GetNodlTokenListUseCase(currenciesRepository, quotesRepository, networksRepository, stakingRepository) - } - @Provides @Singleton fun provideRemoveCurrencyUseCase( diff --git a/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultCurrenciesRepository.kt b/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultCurrenciesRepository.kt index e5b5f561bd..a739407505 100644 --- a/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultCurrenciesRepository.kt +++ b/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultCurrenciesRepository.kt @@ -38,7 +38,6 @@ import com.tangem.domain.wallets.models.UserWalletId import com.tangem.utils.coroutines.CoroutineDispatcherProvider import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.* -import kotlinx.coroutines.launch import kotlinx.coroutines.plus import kotlinx.coroutines.withContext import timber.log.Timber @@ -207,8 +206,9 @@ internal class DefaultCurrenciesRepository( if (userWallet.isMultiCurrency) { getMultiCurrencyWalletCurrenciesUpdates(userWalletId).collect(::send) } else { - val currency = getSingleCurrencyWalletPrimaryCurrency(userWalletId) - send(listOf(currency)) + val currencies = getSingleCurrencyWalletWithCardCurrencies(userWalletId) + + send(currencies) } } } @@ -349,26 +349,34 @@ internal class DefaultCurrenciesRepository( override fun isTokensGrouped(userWalletId: UserWalletId): Flow { return channelFlow { - ensureIsCorrectUserWallet(userWalletId, isMultiCurrencyWalletExpected = true) + val userWallet = getUserWallet(userWalletId) - launch(dispatchers.io) { + if (userWallet.isMultiCurrency) { getSavedUserTokensResponse(userWalletId) - .map { it.group == UserTokensResponse.GroupType.NETWORK } - .collect(::send) + .map { response -> response.group == UserTokensResponse.GroupType.NETWORK } + .distinctUntilChanged() + .onEach { isGrouped -> send(isGrouped) } + .launchIn(scope = this + dispatchers.io) + } else { + send(element = false) } - }.cancellable() + } } override fun isTokensSortedByBalance(userWalletId: UserWalletId): Flow { return channelFlow { - ensureIsCorrectUserWallet(userWalletId, isMultiCurrencyWalletExpected = true) + val userWallet = getUserWallet(userWalletId) - launch(dispatchers.io) { + if (userWallet.isMultiCurrency) { getSavedUserTokensResponse(userWalletId) - .map { it.sort == UserTokensResponse.SortType.BALANCE } - .collect(::send) + .map { response -> response.sort == UserTokensResponse.SortType.BALANCE } + .distinctUntilChanged() + .onEach { isSorted -> send(isSorted) } + .launchIn(scope = this + dispatchers.io) + } else { + send(element = false) } - }.cancellable() + } } override fun isSendBlockedByPendingTransactions( diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetNodlTokenListUseCase.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetNodlTokenListUseCase.kt deleted file mode 100644 index abbe7fa327..0000000000 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetNodlTokenListUseCase.kt +++ /dev/null @@ -1,79 +0,0 @@ -package com.tangem.domain.tokens - -import arrow.core.left -import com.tangem.domain.core.utils.EitherFlow -import com.tangem.domain.staking.repositories.StakingRepository -import com.tangem.domain.tokens.error.TokenListError -import com.tangem.domain.tokens.error.mapper.mapToTokenListError -import com.tangem.domain.tokens.model.CryptoCurrencyStatus -import com.tangem.domain.tokens.model.TokenList -import com.tangem.domain.tokens.operations.CurrenciesStatusesOperations -import com.tangem.domain.tokens.operations.TokenListOperations -import com.tangem.domain.tokens.repository.CurrenciesRepository -import com.tangem.domain.tokens.repository.NetworksRepository -import com.tangem.domain.tokens.repository.QuotesRepository -import com.tangem.domain.wallets.models.UserWalletId -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.flow.emitAll -import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.transformLatest - -/** - * Use case for getting a list of tokens for NODL card - * - * @property currenciesRepository currencies repository - * @property quotesRepository quotes repository - * @property networksRepository networks repository - * @property stakingRepository staking repository - */ -class GetNodlTokenListUseCase( - private val currenciesRepository: CurrenciesRepository, - private val quotesRepository: QuotesRepository, - private val networksRepository: NetworksRepository, - private val stakingRepository: StakingRepository, -) { - - @OptIn(ExperimentalCoroutinesApi::class) - operator fun invoke(userWalletId: UserWalletId): EitherFlow { - return getTokensStatuses(userWalletId).transformLatest { maybeTokens -> - maybeTokens.fold( - ifLeft = { error -> - emit(error.left()) - }, - ifRight = { tokens -> - emitAll(createTokenList(userWalletId, tokens)) - }, - ) - } - } - - private fun getTokensStatuses(userWalletId: UserWalletId): EitherFlow> { - val operations = CurrenciesStatusesOperations( - userWalletId = userWalletId, - currenciesRepository = currenciesRepository, - quotesRepository = quotesRepository, - networksRepository = networksRepository, - stakingRepository = stakingRepository, - ) - - return operations.getCardCurrenciesStatusesFlow() - .map { maybeCurrenciesStatuses -> - maybeCurrenciesStatuses.mapLeft(CurrenciesStatusesOperations.Error::mapToTokenListError) - } - } - - private fun createTokenList( - userWalletId: UserWalletId, - tokens: List, - ): EitherFlow { - val operations = TokenListOperations( - userWalletId = userWalletId, - tokens = tokens, - currenciesRepository = currenciesRepository, - ) - - return operations.getTokenListForSingleCurrencyFlow().map { maybeTokenList -> - maybeTokenList.mapLeft(TokenListOperations.Error::mapToTokenListError) - } - } -} \ No newline at end of file diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/CurrenciesStatusesOperations.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/CurrenciesStatusesOperations.kt index 34ac4153c2..639ccd6991 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/CurrenciesStatusesOperations.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/CurrenciesStatusesOperations.kt @@ -120,50 +120,6 @@ internal class CurrenciesStatusesOperations( return createCurrencyStatus(currency, quotes, networkStatus, yieldBalances) } - fun getCardCurrenciesStatusesFlow(): Flow>> { - return flow { - val nonEmptyCurrencies = recover( - block = { getCurrenciesFromCard(userWalletId) }, - recover = { - emit(it.left()) - return@flow - }, - ).toNonEmptyListOrNull() - - if (nonEmptyCurrencies == null) { - val emptyCurrenciesStatuses = emptyList() - - emit(emptyCurrenciesStatuses.right()) - return@flow - } - - val maybeLoadingCurrenciesStatuses = createCurrenciesStatuses( - currencies = nonEmptyCurrencies, - maybeNetworkStatuses = null, - maybeQuotes = null, - maybeYieldBalances = null, - ) - - emit(maybeLoadingCurrenciesStatuses) - - val (networks, currenciesIds) = getIds(nonEmptyCurrencies) - - val currenciesFlow = combine( - getQuotes(currenciesIds), - getNetworksStatuses(networks), - ) { maybeQuotes, maybeNetworksStatuses -> - createCurrenciesStatuses( - currencies = nonEmptyCurrencies, - maybeQuotes = maybeQuotes, - maybeNetworkStatuses = maybeNetworksStatuses, - maybeYieldBalances = null, - ) - } - - emitAll(currenciesFlow) - } - } - suspend fun getCurrencyStatusFlow(currencyId: CryptoCurrency.ID): Flow> { val currency = recover( block = { getMultiCurrencyWalletCurrency(currencyId) }, @@ -377,12 +333,6 @@ internal class CurrenciesStatusesOperations( ) } - private suspend fun Raise.getCurrenciesFromCard(userWalletId: UserWalletId): List { - return catch({ currenciesRepository.getSingleCurrencyWalletWithCardCurrencies(userWalletId) }) { - raise(Error.DataError(it)) - } - } - private fun getQuotes(tokensIds: NonEmptySet): Flow>> { return quotesRepository.getQuotesUpdates(tokensIds) .map, Either>> { quotes -> diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/TokenListOperations.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/TokenListOperations.kt index 0af48b7898..8f2404de68 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/TokenListOperations.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/TokenListOperations.kt @@ -29,16 +29,6 @@ internal class TokenListOperations( } } - fun getTokenListForSingleCurrencyFlow(): Flow> { - return flow { - emit( - either { - createTokenList() - }, - ) - } - } - private fun Raise.createTokenList(isGrouped: Boolean, isSortedByBalance: Boolean): TokenList { val nonEmptyCurrencies = tokens.toNonEmptyListOrNull() ?: return TokenList.Empty @@ -54,22 +44,6 @@ internal class TokenListOperations( ) } - private fun Raise.createTokenList(): TokenList { - val nonEmptyCurrencies = tokens.toNonEmptyListOrNull() - ?: return TokenList.Empty - - val isAnyTokenLoading = nonEmptyCurrencies.any { it.value is CryptoCurrencyStatus.Loading } - val fiatBalanceOperations = TokenListFiatBalanceOperations(nonEmptyCurrencies, isAnyTokenLoading) - - return createTokenList( - currencies = nonEmptyCurrencies, - fiatBalance = fiatBalanceOperations.calculateFiatBalance(), - isAnyTokenLoading = isAnyTokenLoading, - isGrouped = false, - isSortedByBalance = false, - ) - } - private fun Raise.createTokenList( currencies: NonEmptyList, fiatBalance: TotalFiatBalance, diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/SingleWalletWithTokenContentLoader.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/SingleWalletWithTokenContentLoader.kt index f3829783da..897b44ae92 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/SingleWalletWithTokenContentLoader.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/SingleWalletWithTokenContentLoader.kt @@ -1,12 +1,12 @@ package com.tangem.feature.wallet.presentation.wallet.loaders.implementors import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase -import com.tangem.domain.tokens.GetNodlTokenListUseCase import com.tangem.domain.tokens.RunPolkadotAccountHealthCheckUseCase import com.tangem.domain.wallets.models.UserWallet import com.tangem.feature.wallet.presentation.wallet.analytics.utils.TokenListAnalyticsSender import com.tangem.feature.wallet.presentation.wallet.analytics.utils.WalletWarningsAnalyticsSender import com.tangem.feature.wallet.presentation.wallet.domain.GetMultiWalletWarningsFactory +import com.tangem.feature.wallet.presentation.wallet.domain.MultiWalletTokenListStore import com.tangem.feature.wallet.presentation.wallet.domain.WalletWithFundsChecker import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController import com.tangem.feature.wallet.presentation.wallet.subscribers.MultiWalletWarningsSubscriber @@ -23,7 +23,7 @@ internal class SingleWalletWithTokenContentLoader( private val walletWarningsAnalyticsSender: WalletWarningsAnalyticsSender, private val walletWithFundsChecker: WalletWithFundsChecker, private val getMultiWalletWarningsFactory: GetMultiWalletWarningsFactory, - private val getNodlTokenListUseCase: GetNodlTokenListUseCase, + private val tokenListStore: MultiWalletTokenListStore, private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase, private val runPolkadotAccountHealthCheckUseCase: RunPolkadotAccountHealthCheckUseCase, ) : WalletContentLoader(id = userWallet.walletId) { @@ -36,7 +36,7 @@ internal class SingleWalletWithTokenContentLoader( clickIntents = clickIntents, tokenListAnalyticsSender = tokenListAnalyticsSender, walletWithFundsChecker = walletWithFundsChecker, - getNodlTokenListUseCase = getNodlTokenListUseCase, + tokenListStore = tokenListStore, getSelectedAppCurrencyUseCase = getSelectedAppCurrencyUseCase, runPolkadotAccountHealthCheckUseCase = runPolkadotAccountHealthCheckUseCase, ), diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/SingleWalletWithTokenContentLoaderFactory.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/SingleWalletWithTokenContentLoaderFactory.kt index 5d820846c7..1c433dc756 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/SingleWalletWithTokenContentLoaderFactory.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/SingleWalletWithTokenContentLoaderFactory.kt @@ -1,12 +1,12 @@ package com.tangem.feature.wallet.presentation.wallet.loaders.implementors import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase -import com.tangem.domain.tokens.GetNodlTokenListUseCase import com.tangem.domain.tokens.RunPolkadotAccountHealthCheckUseCase import com.tangem.domain.wallets.models.UserWallet import com.tangem.feature.wallet.presentation.wallet.analytics.utils.TokenListAnalyticsSender import com.tangem.feature.wallet.presentation.wallet.analytics.utils.WalletWarningsAnalyticsSender import com.tangem.feature.wallet.presentation.wallet.domain.GetMultiWalletWarningsFactory +import com.tangem.feature.wallet.presentation.wallet.domain.MultiWalletTokenListStore import com.tangem.feature.wallet.presentation.wallet.domain.WalletWithFundsChecker import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntents @@ -19,7 +19,7 @@ internal class SingleWalletWithTokenContentLoaderFactory @Inject constructor( private val tokenListAnalyticsSender: TokenListAnalyticsSender, private val walletWithFundsChecker: WalletWithFundsChecker, private val getMultiWalletWarningsFactory: GetMultiWalletWarningsFactory, - private val getNodlTokenListUseCase: GetNodlTokenListUseCase, + private val tokenListStore: MultiWalletTokenListStore, private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase, private val walletWarningsAnalyticsSender: WalletWarningsAnalyticsSender, private val runPolkadotAccountHealthCheckUseCase: RunPolkadotAccountHealthCheckUseCase, @@ -33,7 +33,7 @@ internal class SingleWalletWithTokenContentLoaderFactory @Inject constructor( tokenListAnalyticsSender = tokenListAnalyticsSender, walletWithFundsChecker = walletWithFundsChecker, getMultiWalletWarningsFactory = getMultiWalletWarningsFactory, - getNodlTokenListUseCase = getNodlTokenListUseCase, + tokenListStore = tokenListStore, getSelectedAppCurrencyUseCase = getSelectedAppCurrencyUseCase, walletWarningsAnalyticsSender = walletWarningsAnalyticsSender, runPolkadotAccountHealthCheckUseCase = runPolkadotAccountHealthCheckUseCase, diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/SingleWalletWithTokenListSubscriber.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/SingleWalletWithTokenListSubscriber.kt index c8848d06e9..2d37fea506 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/SingleWalletWithTokenListSubscriber.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/SingleWalletWithTokenListSubscriber.kt @@ -2,23 +2,21 @@ package com.tangem.feature.wallet.presentation.wallet.subscribers import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase import com.tangem.domain.core.lce.LceFlow -import com.tangem.domain.core.utils.toLce -import com.tangem.domain.tokens.GetNodlTokenListUseCase import com.tangem.domain.tokens.RunPolkadotAccountHealthCheckUseCase import com.tangem.domain.tokens.error.TokenListError import com.tangem.domain.tokens.model.TokenList import com.tangem.domain.wallets.models.UserWallet import com.tangem.feature.wallet.presentation.wallet.analytics.utils.TokenListAnalyticsSender +import com.tangem.feature.wallet.presentation.wallet.domain.MultiWalletTokenListStore import com.tangem.feature.wallet.presentation.wallet.domain.WalletWithFundsChecker import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntents import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.flow.map @Suppress("LongParameterList") internal class SingleWalletWithTokenListSubscriber( private val userWallet: UserWallet, - private val getNodlTokenListUseCase: GetNodlTokenListUseCase, + private val tokenListStore: MultiWalletTokenListStore, stateHolder: WalletStateController, clickIntents: WalletClickIntents, tokenListAnalyticsSender: TokenListAnalyticsSender, @@ -35,9 +33,9 @@ internal class SingleWalletWithTokenListSubscriber( runPolkadotAccountHealthCheckUseCase = runPolkadotAccountHealthCheckUseCase, ) { - override fun tokenListFlow(coroutineScope: CoroutineScope): LceFlow = - getNodlTokenListUseCase( - userWallet.walletId, - ) - .map { it.toLce() } + override fun tokenListFlow(coroutineScope: CoroutineScope): LceFlow { + tokenListStore.addIfNot(userWallet.walletId, coroutineScope) + + return tokenListStore.getOrThrow(userWallet.walletId) + } } \ No newline at end of file From 42b8de79092a1d90758f6d1856db3d193af1455a Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 29 Oct 2024 13:12:57 +0300 Subject: [PATCH 041/106] Updated on 2026-08-14 --- features/onboarding-v2/impl/build.gradle.kts | 8 +- .../stepper/api/OnboardingStepperComponent.kt | 31 +++++++ .../impl/DefaultOnboardingStepperComponent.kt | 76 +++++++++++++++++ .../v2/stepper/impl/di/ComponentModule.kt | 20 +++++ .../v2/stepper/impl/ui/OnboardingStepper.kt | 83 +++++++++++++++++++ 5 files changed, 216 insertions(+), 2 deletions(-) create mode 100644 features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/stepper/api/OnboardingStepperComponent.kt create mode 100644 features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/stepper/impl/DefaultOnboardingStepperComponent.kt create mode 100644 features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/stepper/impl/di/ComponentModule.kt create mode 100644 features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/stepper/impl/ui/OnboardingStepper.kt diff --git a/features/onboarding-v2/impl/build.gradle.kts b/features/onboarding-v2/impl/build.gradle.kts index e0028b37e7..8cc0843478 100644 --- a/features/onboarding-v2/impl/build.gradle.kts +++ b/features/onboarding-v2/impl/build.gradle.kts @@ -22,6 +22,12 @@ dependencies { implementation(projects.core.utils) implementation(projects.core.ui) implementation(projects.core.res) + implementation(projects.core.decompose) + + /** Domain */ + implementation(projects.domain.models) + implementation(projects.domain.feedback) + implementation(projects.domain.core) /** Tangem libraries */ implementation(deps.tangem.card.core) @@ -31,8 +37,6 @@ dependencies { /** AndroidX libraries */ implementation(deps.androidx.core.ktx) - implementation(deps.androidx.appCompat) - implementation(deps.androidx.constraintLayout) implementation(deps.lifecycle.runtime.ktx) /** Compose libraries */ diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/stepper/api/OnboardingStepperComponent.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/stepper/api/OnboardingStepperComponent.kt new file mode 100644 index 0000000000..3a6cd92582 --- /dev/null +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/stepper/api/OnboardingStepperComponent.kt @@ -0,0 +1,31 @@ +package com.tangem.features.onboarding.v2.stepper.api + +import androidx.annotation.IntRange +import com.tangem.core.decompose.factory.ComponentFactory +import com.tangem.core.ui.decompose.ComposableContentComponent +import com.tangem.core.ui.extensions.TextReference +import com.tangem.domain.models.scan.ScanResponse +import kotlinx.coroutines.flow.StateFlow + +interface OnboardingStepperComponent : ComposableContentComponent { + + data class StepperState( + @IntRange(from = 0) val currentStep: Int, + @IntRange(from = 0) val steps: Int, + val title: TextReference, + ) + + class Params( + val initState: StepperState, + val popBack: () -> Unit, + val scanResponse: ScanResponse, + ) + + val state: StateFlow + + fun changeStep(step: Int) + fun nextStep() + fun changeTitle(title: TextReference) + + interface Factory : ComponentFactory +} \ No newline at end of file diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/stepper/impl/DefaultOnboardingStepperComponent.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/stepper/impl/DefaultOnboardingStepperComponent.kt new file mode 100644 index 0000000000..8e0bfb77ac --- /dev/null +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/stepper/impl/DefaultOnboardingStepperComponent.kt @@ -0,0 +1,76 @@ +package com.tangem.features.onboarding.v2.stepper.impl + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.arkivanov.essenty.instancekeeper.getOrCreateSimple +import com.tangem.core.decompose.context.AppComponentContext +import com.tangem.core.ui.extensions.TextReference +import com.tangem.domain.feedback.GetCardInfoUseCase +import com.tangem.domain.feedback.SendFeedbackEmailUseCase +import com.tangem.domain.feedback.models.FeedbackEmailType +import com.tangem.features.onboarding.v2.stepper.api.OnboardingStepperComponent +import com.tangem.features.onboarding.v2.stepper.impl.ui.OnboardingStepper +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.update +import kotlinx.coroutines.launch + +internal class DefaultOnboardingStepperComponent @AssistedInject constructor( + @Assisted val context: AppComponentContext, + @Assisted val params: OnboardingStepperComponent.Params, + private val getCardInfoUseCase: GetCardInfoUseCase, + private val sendFeedbackEmailUseCase: SendFeedbackEmailUseCase, +) : OnboardingStepperComponent, AppComponentContext by context { + + override val state = instanceKeeper.getOrCreateSimple { MutableStateFlow(params.initState) } + + override fun changeStep(step: Int) { + state.update { it.copy(currentStep = step.coerceAtLeast(minimumValue = 0)) } + } + + override fun nextStep() { + state.update { state -> + if (state.currentStep < state.steps - 1) { + state.copy(currentStep = state.currentStep + 1) + } else { + state + } + } + } + + override fun changeTitle(title: TextReference) { + state.update { it.copy(title = title) } + } + + private fun openSupport() { + componentScope.launch { + val cardInfo = getCardInfoUseCase(params.scanResponse).getOrNull() ?: return@launch + sendFeedbackEmailUseCase(FeedbackEmailType.DirectUserRequest(cardInfo)) + } + } + + @Composable + override fun Content(modifier: Modifier) { + val state by state.collectAsStateWithLifecycle() + + OnboardingStepper( + state = state, + onBackClick = remember(this) { params.popBack }, + onSupportButtonClick = remember(this) { ::openSupport }, + modifier = modifier, + ) + } + + @AssistedFactory + interface Factory : OnboardingStepperComponent.Factory { + override fun create( + context: AppComponentContext, + params: OnboardingStepperComponent.Params, + ): DefaultOnboardingStepperComponent + } +} \ No newline at end of file diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/stepper/impl/di/ComponentModule.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/stepper/impl/di/ComponentModule.kt new file mode 100644 index 0000000000..e0401a486d --- /dev/null +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/stepper/impl/di/ComponentModule.kt @@ -0,0 +1,20 @@ +package com.tangem.features.onboarding.v2.stepper.impl.di + +import com.tangem.features.onboarding.v2.stepper.api.OnboardingStepperComponent +import com.tangem.features.onboarding.v2.stepper.impl.DefaultOnboardingStepperComponent +import dagger.Binds +import dagger.Module +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +internal interface ComponentModule { + + @Binds + @Singleton + fun bindOnboardingStepperComponent( + factory: DefaultOnboardingStepperComponent.Factory, + ): OnboardingStepperComponent.Factory +} \ No newline at end of file diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/stepper/impl/ui/OnboardingStepper.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/stepper/impl/ui/OnboardingStepper.kt new file mode 100644 index 0000000000..a4d735e82b --- /dev/null +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/stepper/impl/ui/OnboardingStepper.kt @@ -0,0 +1,83 @@ +package com.tangem.features.onboarding.v2.stepper.impl.ui + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.* +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.StrokeCap +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.tangem.core.ui.components.appbar.TangemTopAppBar +import com.tangem.core.ui.components.appbar.models.TopAppBarButtonUM +import com.tangem.core.ui.components.progressbar.LinearProgressIndicator +import com.tangem.core.ui.extensions.resourceReference +import com.tangem.core.ui.res.TangemAnimations +import com.tangem.core.ui.res.TangemTheme +import com.tangem.core.ui.res.TangemThemePreview +import com.tangem.features.onboarding.v2.impl.R +import com.tangem.features.onboarding.v2.stepper.api.OnboardingStepperComponent + +@Composable +internal fun OnboardingStepper( + state: OnboardingStepperComponent.StepperState, + onBackClick: () -> Unit, + onSupportButtonClick: () -> Unit, + modifier: Modifier = Modifier, +) { + val fraction = state.currentStep.toFloat() / state.steps.coerceAtLeast(1) + val animatedIndicatorFraction by TangemAnimations.horizontalIndicatorAsState(targetFraction = fraction) + + Column( + modifier = modifier, + verticalArrangement = Arrangement.spacedBy(8.dp), + ) { + TangemTopAppBar( + startButton = TopAppBarButtonUM.Back(onBackClick), + endButton = TopAppBarButtonUM(iconRes = R.drawable.ic_chat_24, onIconClicked = onSupportButtonClick) + .takeIf { state.steps != state.currentStep }, + title = if (state.steps == state.currentStep) { + resourceReference(R.string.common_done) + } else { + state.title + }, + containerColor = TangemTheme.colors.background.primary, + modifier = modifier, + ) + + LinearProgressIndicator( + modifier = Modifier + .padding(horizontal = 16.dp) + .height(4.dp) + .fillMaxWidth(), + progress = { animatedIndicatorFraction }, + color = TangemTheme.colors.icon.primary1, + backgroundColor = TangemTheme.colors.background.tertiary, + strokeCap = StrokeCap.Round, + ) + } +} + +@Preview +@Composable +private fun Preview() { + TangemThemePreview { + Box( + Modifier + .fillMaxSize() + .background(TangemTheme.colors.background.primary), + ) { + OnboardingStepper( + modifier = Modifier.align(Alignment.TopCenter), + state = OnboardingStepperComponent.StepperState( + currentStep = 2, + steps = 3, + title = resourceReference(R.string.common_done), + ), + onBackClick = {}, + onSupportButtonClick = {}, + ) + } + } +} \ No newline at end of file From 06f2d51e87eaf7118caaf29748e8dc2ce0e253f3 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 29 Oct 2024 12:14:56 +0400 Subject: [PATCH 042/106] Updated on 2026-08-14 --- .../tangem/tap/routing/utils/ChildFactory.kt | 9 +++ .../com/tangem/common/routing/AppRoute.kt | 9 +-- .../GetSelectedAppCurrencyUseCase.kt | 9 +-- features/onramp/api/build.gradle.kts | 3 + .../onramp/component/BuyCryptoComponent.kt | 22 +++++++ features/onramp/impl/build.gradle.kts | 3 + .../onramp/buy/DefaultBuyCryptoComponent.kt | 65 +++++++++++++++++++ .../onramp/buy/di/BuyCryptoComponentModule.kt | 18 +++++ 8 files changed, 130 insertions(+), 8 deletions(-) create mode 100644 features/onramp/api/src/main/kotlin/com/tangem/features/onramp/component/BuyCryptoComponent.kt create mode 100644 features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/buy/DefaultBuyCryptoComponent.kt create mode 100644 features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/buy/di/BuyCryptoComponentModule.kt diff --git a/app/src/main/java/com/tangem/tap/routing/utils/ChildFactory.kt b/app/src/main/java/com/tangem/tap/routing/utils/ChildFactory.kt index cca2474fa1..06b53a8fd3 100644 --- a/app/src/main/java/com/tangem/tap/routing/utils/ChildFactory.kt +++ b/app/src/main/java/com/tangem/tap/routing/utils/ChildFactory.kt @@ -11,6 +11,7 @@ import com.tangem.features.disclaimer.api.components.DisclaimerComponent import com.tangem.features.managetokens.component.ManageTokensComponent import com.tangem.features.managetokens.component.ManageTokensSource import com.tangem.features.markets.details.MarketsTokenDetailsComponent +import com.tangem.features.onramp.component.BuyCryptoComponent import com.tangem.features.onramp.component.OnrampComponent import com.tangem.features.pushnotifications.api.navigation.PushNotificationsRouter import com.tangem.features.send.api.navigation.SendRouter @@ -48,6 +49,7 @@ internal class ChildFactory @Inject constructor( private val manageTokensComponentFactory: ManageTokensComponent.Factory, private val marketsTokenDetailsComponentFactory: MarketsTokenDetailsComponent.Factory, private val onrampComponentFactory: OnrampComponent.Factory, + private val buyCryptoComponentFactory: BuyCryptoComponent.Factory, private val sendRouter: SendRouter, private val tokenDetailsRouter: TokenDetailsRouter, private val walletRouter: WalletRouter, @@ -195,6 +197,13 @@ internal class ChildFactory @Inject constructor( componentFactory = onrampComponentFactory, ) } + is AppRoute.BuyCrypto -> { + route.asComponentChild( + contextProvider = contextProvider(route, contextFactory), + params = BuyCryptoComponent.Params(userWalletId = route.userWalletId), + componentFactory = buyCryptoComponentFactory, + ) + } } } diff --git a/common/routing/src/main/kotlin/com/tangem/common/routing/AppRoute.kt b/common/routing/src/main/kotlin/com/tangem/common/routing/AppRoute.kt index 94e8f8487c..c10a008033 100644 --- a/common/routing/src/main/kotlin/com/tangem/common/routing/AppRoute.kt +++ b/common/routing/src/main/kotlin/com/tangem/common/routing/AppRoute.kt @@ -117,10 +117,6 @@ sealed class AppRoute(val path: String) : Route { ) : AppRoute(path = "/details/security"), RouteBundleParams { override fun getBundle(): Bundle = bundle(serializer()) - - companion object { - const val USER_WALLET_ID_KEY = "userWalletId" - } } @Serializable @@ -291,4 +287,9 @@ sealed class AppRoute(val path: String) : Route { @Serializable data object Onramp : AppRoute(path = "/onramp") + + @Serializable + data class BuyCrypto( + val userWalletId: UserWalletId, + ) : AppRoute(path = "/buy_crypto/${userWalletId.stringValue}") } \ No newline at end of file diff --git a/domain/app-currency/src/main/kotlin/com/tangem/domain/appcurrency/GetSelectedAppCurrencyUseCase.kt b/domain/app-currency/src/main/kotlin/com/tangem/domain/appcurrency/GetSelectedAppCurrencyUseCase.kt index 4bd3a52e99..179973c048 100644 --- a/domain/app-currency/src/main/kotlin/com/tangem/domain/appcurrency/GetSelectedAppCurrencyUseCase.kt +++ b/domain/app-currency/src/main/kotlin/com/tangem/domain/appcurrency/GetSelectedAppCurrencyUseCase.kt @@ -6,10 +6,7 @@ import arrow.core.right import com.tangem.domain.appcurrency.error.SelectedAppCurrencyError import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.appcurrency.repository.AppCurrencyRepository -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.catch -import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.onEmpty +import kotlinx.coroutines.flow.* class GetSelectedAppCurrencyUseCase( private val appCurrencyRepository: AppCurrencyRepository, @@ -21,4 +18,8 @@ class GetSelectedAppCurrencyUseCase( .catch { emit(SelectedAppCurrencyError.DataError(it).left()) } .onEmpty { emit(SelectedAppCurrencyError.NoAppCurrencySelected.left()) } } + + suspend fun invokeSync(): Either { + return invoke().firstOrNull() ?: SelectedAppCurrencyError.NoAppCurrencySelected.left() + } } \ No newline at end of file diff --git a/features/onramp/api/build.gradle.kts b/features/onramp/api/build.gradle.kts index a241d32468..cde16c9b92 100644 --- a/features/onramp/api/build.gradle.kts +++ b/features/onramp/api/build.gradle.kts @@ -14,6 +14,9 @@ dependencies { implementation(projects.core.decompose) implementation(projects.core.ui) + /* Project - Domain */ + implementation(projects.domain.wallets.models) + /* Compose */ implementation(deps.compose.runtime) } \ No newline at end of file diff --git a/features/onramp/api/src/main/kotlin/com/tangem/features/onramp/component/BuyCryptoComponent.kt b/features/onramp/api/src/main/kotlin/com/tangem/features/onramp/component/BuyCryptoComponent.kt new file mode 100644 index 0000000000..c627160e26 --- /dev/null +++ b/features/onramp/api/src/main/kotlin/com/tangem/features/onramp/component/BuyCryptoComponent.kt @@ -0,0 +1,22 @@ +package com.tangem.features.onramp.component + +import com.tangem.core.decompose.factory.ComponentFactory +import com.tangem.core.ui.decompose.ComposableContentComponent +import com.tangem.domain.wallets.models.UserWalletId + +/** + * Buy crypto component + * +[REDACTED_AUTHOR] + */ +interface BuyCryptoComponent : ComposableContentComponent { + + interface Factory : ComponentFactory + + /** + * Params + * + * @property userWalletId user wallet id + */ + data class Params(val userWalletId: UserWalletId) +} \ No newline at end of file diff --git a/features/onramp/impl/build.gradle.kts b/features/onramp/impl/build.gradle.kts index 8de571b7a7..042df5ae39 100644 --- a/features/onramp/impl/build.gradle.kts +++ b/features/onramp/impl/build.gradle.kts @@ -28,8 +28,10 @@ dependencies { implementation(projects.domain.appCurrency.models) implementation(projects.domain.balanceHiding) implementation(projects.domain.balanceHiding.models) + implementation(projects.domain.legacy) implementation(projects.domain.tokens) implementation(projects.domain.tokens.models) + implementation(projects.domain.wallets) implementation(projects.domain.wallets.models) /** DI */ @@ -54,5 +56,6 @@ dependencies { /** Other */ implementation(deps.decompose.ext.compose) implementation(deps.kotlin.immutable.collections) + implementation(deps.reKotlin) implementation(deps.timber) } \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/buy/DefaultBuyCryptoComponent.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/buy/DefaultBuyCryptoComponent.kt new file mode 100644 index 0000000000..1c463d0cdf --- /dev/null +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/buy/DefaultBuyCryptoComponent.kt @@ -0,0 +1,65 @@ +package com.tangem.features.onramp.buy + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.Stable +import androidx.compose.ui.Modifier +import arrow.core.getOrElse +import com.tangem.core.decompose.context.AppComponentContext +import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase +import com.tangem.domain.appcurrency.model.AppCurrency +import com.tangem.domain.redux.ReduxStateHolder +import com.tangem.domain.tokens.legacy.TradeCryptoAction +import com.tangem.domain.tokens.model.CryptoCurrencyStatus +import com.tangem.domain.wallets.usecase.GetWalletsUseCase +import com.tangem.features.onramp.component.BuyCryptoComponent +import com.tangem.features.onramp.impl.R +import com.tangem.features.onramp.selecttoken.OnrampSelectTokenComponent +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject +import kotlinx.coroutines.launch + +@Stable +internal class DefaultBuyCryptoComponent @AssistedInject constructor( + onrampSelectTokenComponentFactory: OnrampSelectTokenComponent.Factory, + @Assisted private val appComponentContext: AppComponentContext, + @Assisted private val params: BuyCryptoComponent.Params, + private val reduxStateHolder: ReduxStateHolder, + private val getWalletsUseCase: GetWalletsUseCase, + private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase, +) : BuyCryptoComponent { + + private val selectTokenComponent: OnrampSelectTokenComponent = onrampSelectTokenComponentFactory.create( + context = appComponentContext, + params = OnrampSelectTokenComponent.Params( + hasSearchBar = true, + userWalletId = params.userWalletId, + titleResId = R.string.common_buy, + onTokenClick = ::onTokenClick, + ), + ) + + @Composable + override fun Content(modifier: Modifier) { + selectTokenComponent.Content(modifier = modifier) + } + + private fun onTokenClick(status: CryptoCurrencyStatus) { + appComponentContext.componentScope.launch { + reduxStateHolder.dispatch( + TradeCryptoAction.Buy( + userWallet = getWalletsUseCase.invokeSync().first { it.walletId == params.userWalletId }, + cryptoCurrencyStatus = status, + appCurrencyCode = getSelectedAppCurrencyUseCase.invokeSync() + .getOrElse { AppCurrency.Default }.code, + ), + ) + } + } + + @AssistedFactory + interface Factory : BuyCryptoComponent.Factory { + + override fun create(context: AppComponentContext, params: BuyCryptoComponent.Params): DefaultBuyCryptoComponent + } +} \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/buy/di/BuyCryptoComponentModule.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/buy/di/BuyCryptoComponentModule.kt new file mode 100644 index 0000000000..d45a326a89 --- /dev/null +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/buy/di/BuyCryptoComponentModule.kt @@ -0,0 +1,18 @@ +package com.tangem.features.onramp.buy.di + +import com.tangem.features.onramp.buy.DefaultBuyCryptoComponent +import com.tangem.features.onramp.component.BuyCryptoComponent +import dagger.Binds +import dagger.Module +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +internal interface BuyCryptoComponentModule { + + @Binds + @Singleton + fun bindBuyCryptoComponentFactory(factory: DefaultBuyCryptoComponent.Factory): BuyCryptoComponent.Factory +} \ No newline at end of file From 1698771cfdbfe18878dfa95618eb04c76abce778 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 29 Oct 2024 14:20:14 +0400 Subject: [PATCH 043/106] Updated on 2026-08-14 --- .../tangem/tap/routing/utils/ChildFactory.kt | 9 +++ .../com/tangem/common/routing/AppRoute.kt | 5 ++ .../onramp/component/SellCryptoComponent.kt | 22 +++++++ .../onramp/sell/DefaultSellCryptoComponent.kt | 65 +++++++++++++++++++ .../sell/di/SellCryptoComponentModule.kt | 18 +++++ 5 files changed, 119 insertions(+) create mode 100644 features/onramp/api/src/main/kotlin/com/tangem/features/onramp/component/SellCryptoComponent.kt create mode 100644 features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/sell/DefaultSellCryptoComponent.kt create mode 100644 features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/sell/di/SellCryptoComponentModule.kt diff --git a/app/src/main/java/com/tangem/tap/routing/utils/ChildFactory.kt b/app/src/main/java/com/tangem/tap/routing/utils/ChildFactory.kt index 06b53a8fd3..5731dab42f 100644 --- a/app/src/main/java/com/tangem/tap/routing/utils/ChildFactory.kt +++ b/app/src/main/java/com/tangem/tap/routing/utils/ChildFactory.kt @@ -13,6 +13,7 @@ import com.tangem.features.managetokens.component.ManageTokensSource import com.tangem.features.markets.details.MarketsTokenDetailsComponent import com.tangem.features.onramp.component.BuyCryptoComponent import com.tangem.features.onramp.component.OnrampComponent +import com.tangem.features.onramp.component.SellCryptoComponent import com.tangem.features.pushnotifications.api.navigation.PushNotificationsRouter import com.tangem.features.send.api.navigation.SendRouter import com.tangem.features.staking.api.navigation.StakingRouter @@ -50,6 +51,7 @@ internal class ChildFactory @Inject constructor( private val marketsTokenDetailsComponentFactory: MarketsTokenDetailsComponent.Factory, private val onrampComponentFactory: OnrampComponent.Factory, private val buyCryptoComponentFactory: BuyCryptoComponent.Factory, + private val sellCryptoComponentFactory: SellCryptoComponent.Factory, private val sendRouter: SendRouter, private val tokenDetailsRouter: TokenDetailsRouter, private val walletRouter: WalletRouter, @@ -204,6 +206,13 @@ internal class ChildFactory @Inject constructor( componentFactory = buyCryptoComponentFactory, ) } + is AppRoute.SellCrypto -> { + route.asComponentChild( + contextProvider = contextProvider(route, contextFactory), + params = SellCryptoComponent.Params(userWalletId = route.userWalletId), + componentFactory = sellCryptoComponentFactory, + ) + } } } diff --git a/common/routing/src/main/kotlin/com/tangem/common/routing/AppRoute.kt b/common/routing/src/main/kotlin/com/tangem/common/routing/AppRoute.kt index c10a008033..cb8be40a85 100644 --- a/common/routing/src/main/kotlin/com/tangem/common/routing/AppRoute.kt +++ b/common/routing/src/main/kotlin/com/tangem/common/routing/AppRoute.kt @@ -292,4 +292,9 @@ sealed class AppRoute(val path: String) : Route { data class BuyCrypto( val userWalletId: UserWalletId, ) : AppRoute(path = "/buy_crypto/${userWalletId.stringValue}") + + @Serializable + data class SellCrypto( + val userWalletId: UserWalletId, + ) : AppRoute(path = "/sell_crypto/${userWalletId.stringValue}") } \ No newline at end of file diff --git a/features/onramp/api/src/main/kotlin/com/tangem/features/onramp/component/SellCryptoComponent.kt b/features/onramp/api/src/main/kotlin/com/tangem/features/onramp/component/SellCryptoComponent.kt new file mode 100644 index 0000000000..ce340b8c07 --- /dev/null +++ b/features/onramp/api/src/main/kotlin/com/tangem/features/onramp/component/SellCryptoComponent.kt @@ -0,0 +1,22 @@ +package com.tangem.features.onramp.component + +import com.tangem.core.decompose.factory.ComponentFactory +import com.tangem.core.ui.decompose.ComposableContentComponent +import com.tangem.domain.wallets.models.UserWalletId + +/** + * Sell crypto component + * +[REDACTED_AUTHOR] + */ +interface SellCryptoComponent : ComposableContentComponent { + + interface Factory : ComponentFactory + + /** + * Params + * + * @property userWalletId user wallet id + */ + data class Params(val userWalletId: UserWalletId) +} \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/sell/DefaultSellCryptoComponent.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/sell/DefaultSellCryptoComponent.kt new file mode 100644 index 0000000000..b1a3589e6c --- /dev/null +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/sell/DefaultSellCryptoComponent.kt @@ -0,0 +1,65 @@ +package com.tangem.features.onramp.sell + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.Stable +import androidx.compose.ui.Modifier +import arrow.core.getOrElse +import com.tangem.core.decompose.context.AppComponentContext +import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase +import com.tangem.domain.appcurrency.model.AppCurrency +import com.tangem.domain.redux.ReduxStateHolder +import com.tangem.domain.tokens.legacy.TradeCryptoAction +import com.tangem.domain.tokens.model.CryptoCurrencyStatus +import com.tangem.features.onramp.component.SellCryptoComponent +import com.tangem.features.onramp.impl.R +import com.tangem.features.onramp.selecttoken.OnrampSelectTokenComponent +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject +import kotlinx.coroutines.launch + +@Stable +internal class DefaultSellCryptoComponent @AssistedInject constructor( + onrampSelectTokenComponentFactory: OnrampSelectTokenComponent.Factory, + @Assisted private val appComponentContext: AppComponentContext, + @Assisted private val params: SellCryptoComponent.Params, + private val reduxStateHolder: ReduxStateHolder, + private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase, +) : SellCryptoComponent { + + private val selectTokenComponent: OnrampSelectTokenComponent = onrampSelectTokenComponentFactory.create( + context = appComponentContext, + params = OnrampSelectTokenComponent.Params( + hasSearchBar = true, + userWalletId = params.userWalletId, + titleResId = R.string.common_sell, + onTokenClick = ::onTokenClick, + ), + ) + + @Composable + override fun Content(modifier: Modifier) { + selectTokenComponent.Content(modifier = modifier) + } + + private fun onTokenClick(status: CryptoCurrencyStatus) { + appComponentContext.componentScope.launch { + reduxStateHolder.dispatch( + TradeCryptoAction.Sell( + cryptoCurrencyStatus = status, + appCurrencyCode = getSelectedAppCurrencyUseCase.invokeSync() + .getOrElse { AppCurrency.Default }.code, + ), + ) + } + } + + @AssistedFactory + interface Factory : SellCryptoComponent.Factory { + + override fun create( + context: AppComponentContext, + params: SellCryptoComponent.Params, + ): DefaultSellCryptoComponent + } +} \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/sell/di/SellCryptoComponentModule.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/sell/di/SellCryptoComponentModule.kt new file mode 100644 index 0000000000..cb2ec7c8f6 --- /dev/null +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/sell/di/SellCryptoComponentModule.kt @@ -0,0 +1,18 @@ +package com.tangem.features.onramp.sell.di + +import com.tangem.features.onramp.component.SellCryptoComponent +import com.tangem.features.onramp.sell.DefaultSellCryptoComponent +import dagger.Binds +import dagger.Module +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +internal interface SellCryptoComponentModule { + + @Binds + @Singleton + fun bindSellCryptoComponentFactory(factory: DefaultSellCryptoComponent.Factory): SellCryptoComponent.Factory +} \ No newline at end of file From ccdd5fa0838eeec3d8228658e7d72824de7bccbf Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 29 Oct 2024 14:38:22 +0400 Subject: [PATCH 044/106] Updated on 2026-08-14 --- .../com/tangem/tap/common/feature/Feature.kt | 8 --- .../common/redux/global/GlobalMiddleware.kt | 16 ++--- .../exchangeServices/BuyExchangeService.kt | 60 ------------------- .../exchangeServices/CardExchangeRules.kt | 6 -- .../CurrencyExchangeManager.kt | 2 - .../exchangeServices/ExchangeService.kt | 7 +-- .../mercuryo/MercuryoService.kt | 2 - .../moonpay/MoonPayService.kt | 2 - 8 files changed, 7 insertions(+), 96 deletions(-) delete mode 100644 app/src/main/java/com/tangem/tap/common/feature/Feature.kt delete mode 100644 app/src/main/java/com/tangem/tap/network/exchangeServices/BuyExchangeService.kt diff --git a/app/src/main/java/com/tangem/tap/common/feature/Feature.kt b/app/src/main/java/com/tangem/tap/common/feature/Feature.kt deleted file mode 100644 index 1eb07546b4..0000000000 --- a/app/src/main/java/com/tangem/tap/common/feature/Feature.kt +++ /dev/null @@ -1,8 +0,0 @@ -package com.tangem.tap.common.feature - -/** -[REDACTED_AUTHOR] - */ -interface Feature { - fun featureIsSwitchedOn(): Boolean -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalMiddleware.kt b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalMiddleware.kt index 636b4f40b1..c4366e8215 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalMiddleware.kt @@ -12,7 +12,6 @@ import com.tangem.domain.models.scan.ScanResponse import com.tangem.domain.redux.StateDialog import com.tangem.tap.common.extensions.* import com.tangem.tap.common.redux.AppState -import com.tangem.tap.network.exchangeServices.BuyExchangeService import com.tangem.tap.network.exchangeServices.CardExchangeRules import com.tangem.tap.network.exchangeServices.CurrencyExchangeManager import com.tangem.tap.network.exchangeServices.ExchangeService @@ -148,15 +147,10 @@ private fun makeSellExchangeService(environmentConfig: EnvironmentConfig): Excha } private fun makeBuyExchangeService(environmentConfig: EnvironmentConfig): ExchangeService { - return BuyExchangeService( - mercuryoService = makeMercuryoExchangeService(environmentConfig), + return MercuryoService( + environment = MercuryoEnvironment.prod( + widgetId = environmentConfig.mercuryoWidgetId, + secret = environmentConfig.mercuryoSecret, + ), ) -} - -private fun makeMercuryoExchangeService(environmentConfig: EnvironmentConfig): MercuryoService { - val mercuryoEnvironment = MercuryoEnvironment.prod( - environmentConfig.mercuryoWidgetId, - environmentConfig.mercuryoSecret, - ) - return MercuryoService(mercuryoEnvironment) } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/network/exchangeServices/BuyExchangeService.kt b/app/src/main/java/com/tangem/tap/network/exchangeServices/BuyExchangeService.kt deleted file mode 100644 index a8861eed2f..0000000000 --- a/app/src/main/java/com/tangem/tap/network/exchangeServices/BuyExchangeService.kt +++ /dev/null @@ -1,60 +0,0 @@ -package com.tangem.tap.network.exchangeServices - -import com.tangem.domain.models.scan.ScanResponse -import com.tangem.domain.tokens.model.CryptoCurrency -import com.tangem.tap.domain.model.Currency -import com.tangem.tap.network.exchangeServices.mercuryo.MercuryoService -import com.tangem.tap.scope -import kotlinx.coroutines.launch - -/** -[REDACTED_AUTHOR] - * Temporary wrapper for the buy services. Service switches based on selected product type. - */ -internal class BuyExchangeService( - private val mercuryoService: MercuryoService, -) : ExchangeService, ExchangeUrlBuilder { - - init { - scope.launch { - mercuryoService.update() - } - } - - private val currentService: ExchangeService = mercuryoService - - override suspend fun update() { - currentService.update() - } - - override fun featureIsSwitchedOn(): Boolean = currentService.featureIsSwitchedOn() - - override fun isBuyAllowed(): Boolean = currentService.isBuyAllowed() - - override fun isSellAllowed(): Boolean = currentService.isSellAllowed() - - override fun availableForBuy(scanResponse: ScanResponse, currency: Currency): Boolean = - currentService.availableForBuy(scanResponse, currency) - - override fun availableForSell(currency: Currency): Boolean = currentService.availableForSell(currency) - - override fun getUrl( - action: CurrencyExchangeManager.Action, - cryptoCurrency: CryptoCurrency, - fiatCurrencyName: String, - walletAddress: String, - isDarkTheme: Boolean, - ): String? { - return currentService.getUrl( - action, - cryptoCurrency, - fiatCurrencyName, - walletAddress, - isDarkTheme, - ) - } - - override fun getSellCryptoReceiptUrl(action: CurrencyExchangeManager.Action, transactionId: String): String? { - return currentService.getSellCryptoReceiptUrl(action, transactionId) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/network/exchangeServices/CardExchangeRules.kt b/app/src/main/java/com/tangem/tap/network/exchangeServices/CardExchangeRules.kt index 696f9288bc..0795969b5c 100644 --- a/app/src/main/java/com/tangem/tap/network/exchangeServices/CardExchangeRules.kt +++ b/app/src/main/java/com/tangem/tap/network/exchangeServices/CardExchangeRules.kt @@ -13,12 +13,6 @@ class CardExchangeRules( val cardProvider: () -> CardDTO?, ) : ExchangeRules { - override fun featureIsSwitchedOn(): Boolean { - val card = cardProvider() ?: return false - - return !card.isStart2Coin - } - override fun isBuyAllowed(): Boolean { val card = cardProvider() ?: return false diff --git a/app/src/main/java/com/tangem/tap/network/exchangeServices/CurrencyExchangeManager.kt b/app/src/main/java/com/tangem/tap/network/exchangeServices/CurrencyExchangeManager.kt index 2008fae3cc..71c317e080 100644 --- a/app/src/main/java/com/tangem/tap/network/exchangeServices/CurrencyExchangeManager.kt +++ b/app/src/main/java/com/tangem/tap/network/exchangeServices/CurrencyExchangeManager.kt @@ -30,8 +30,6 @@ class CurrencyExchangeManager( private val primaryRules: ExchangeRules, ) : ExchangeService { - override fun featureIsSwitchedOn(): Boolean = primaryRules.featureIsSwitchedOn() - override suspend fun update() { buyService.update() sellService.update() diff --git a/app/src/main/java/com/tangem/tap/network/exchangeServices/ExchangeService.kt b/app/src/main/java/com/tangem/tap/network/exchangeServices/ExchangeService.kt index 1cfd8502ed..495bcec410 100644 --- a/app/src/main/java/com/tangem/tap/network/exchangeServices/ExchangeService.kt +++ b/app/src/main/java/com/tangem/tap/network/exchangeServices/ExchangeService.kt @@ -2,7 +2,6 @@ package com.tangem.tap.network.exchangeServices import com.tangem.domain.models.scan.ScanResponse import com.tangem.domain.tokens.model.CryptoCurrency -import com.tangem.tap.common.feature.Feature import com.tangem.tap.domain.model.Currency interface Exchanger { @@ -12,12 +11,11 @@ interface Exchanger { fun availableForSell(currency: Currency): Boolean } -interface ExchangeService : Feature, Exchanger, ExchangeUrlBuilder { +interface ExchangeService : Exchanger, ExchangeUrlBuilder { suspend fun update() companion object { fun dummy(): ExchangeService = object : ExchangeService { - override fun featureIsSwitchedOn(): Boolean = false override suspend fun update() {} override fun isBuyAllowed(): Boolean = false override fun isSellAllowed(): Boolean = false @@ -39,11 +37,10 @@ interface ExchangeService : Feature, Exchanger, ExchangeUrlBuilder { } } -interface ExchangeRules : Feature, Exchanger { +interface ExchangeRules : Exchanger { companion object { fun dummy(): ExchangeRules = object : ExchangeRules { - override fun featureIsSwitchedOn(): Boolean = false override fun isBuyAllowed(): Boolean = false override fun isSellAllowed(): Boolean = false override fun availableForBuy(scanResponse: ScanResponse, currency: Currency): Boolean = false diff --git a/app/src/main/java/com/tangem/tap/network/exchangeServices/mercuryo/MercuryoService.kt b/app/src/main/java/com/tangem/tap/network/exchangeServices/mercuryo/MercuryoService.kt index 8bcc8168e9..f4ceb2e9d8 100644 --- a/app/src/main/java/com/tangem/tap/network/exchangeServices/mercuryo/MercuryoService.kt +++ b/app/src/main/java/com/tangem/tap/network/exchangeServices/mercuryo/MercuryoService.kt @@ -23,8 +23,6 @@ internal class MercuryoService(private val environment: MercuryoEnvironment) : E private val availableMercuryoCurrencies = CopyOnWriteArrayList() - override fun featureIsSwitchedOn(): Boolean = true - override fun isBuyAllowed(): Boolean = true override fun isSellAllowed(): Boolean = false diff --git a/app/src/main/java/com/tangem/tap/network/exchangeServices/moonpay/MoonPayService.kt b/app/src/main/java/com/tangem/tap/network/exchangeServices/moonpay/MoonPayService.kt index de34810420..26b26f0b61 100644 --- a/app/src/main/java/com/tangem/tap/network/exchangeServices/moonpay/MoonPayService.kt +++ b/app/src/main/java/com/tangem/tap/network/exchangeServices/moonpay/MoonPayService.kt @@ -32,8 +32,6 @@ class MoonPayService( private var status: MoonPayStatus? = null - override fun featureIsSwitchedOn(): Boolean = true - override suspend fun update() { withIOContext { performRequest { From 488d1e281574a04150a06f612bf509e87cf71be4 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 30 Oct 2024 10:56:02 +0300 Subject: [PATCH 045/106] Updated on 2026-08-14 --- .../com/tangem/tap/ApplicationEntryPoint.kt | 3 ++ .../java/com/tangem/tap/TangemApplication.kt | 5 ++ .../features/onboarding/OnboardingHelper.kt | 4 ++ .../tap/proxy/redux/DaggerGraphState.kt | 2 + .../tangem/tap/routing/utils/ChildFactory.kt | 9 ++++ .../com/tangem/common/routing/AppRoute.kt | 4 ++ features/onboarding-v2/api/build.gradle.kts | 2 +- .../v2/OnboardingV2FeatureToggles.kt | 2 +- .../v2/entry/OnboardingEntryComponent.kt | 3 +- .../v2/DefaultOnboardingV2FeatureToggles.kt | 2 +- .../impl/DefaultOnboardingEntryComponent.kt | 51 +++++++++++++++++++ .../v2/entry/impl/di/ComponentModule.kt | 20 ++++++++ .../v2/entry/impl/ui/OnboardingEntry.kt | 44 ++++++++++++++++ .../stepper/api/OnboardingStepperComponent.kt | 2 +- 14 files changed, 148 insertions(+), 5 deletions(-) create mode 100644 features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/entry/impl/DefaultOnboardingEntryComponent.kt create mode 100644 features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/entry/impl/di/ComponentModule.kt create mode 100644 features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/entry/impl/ui/OnboardingEntry.kt diff --git a/app/src/main/java/com/tangem/tap/ApplicationEntryPoint.kt b/app/src/main/java/com/tangem/tap/ApplicationEntryPoint.kt index 923313f69f..b116caf952 100644 --- a/app/src/main/java/com/tangem/tap/ApplicationEntryPoint.kt +++ b/app/src/main/java/com/tangem/tap/ApplicationEntryPoint.kt @@ -28,6 +28,7 @@ import com.tangem.domain.walletmanager.WalletManagersFacade import com.tangem.domain.wallets.legacy.UserWalletsListManager import com.tangem.domain.wallets.repository.WalletsRepository import com.tangem.domain.wallets.usecase.GenerateWalletNameUseCase +import com.tangem.features.onboarding.v2.OnboardingV2FeatureToggles import com.tangem.features.onramp.OnrampFeatureToggles import com.tangem.tap.common.log.TangemAppLoggerInitializer import com.tangem.tap.domain.scanCard.CardScanningFeatureToggles @@ -110,4 +111,6 @@ interface ApplicationEntryPoint { fun getGetUserCountryCodeUseCase(): GetUserCountryUseCase fun getOnrampFeatureToggles(): OnrampFeatureToggles + + fun getOnboardingV2FeatureToggles(): OnboardingV2FeatureToggles } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/TangemApplication.kt b/app/src/main/java/com/tangem/tap/TangemApplication.kt index c30a00c30d..0e2e83f67f 100644 --- a/app/src/main/java/com/tangem/tap/TangemApplication.kt +++ b/app/src/main/java/com/tangem/tap/TangemApplication.kt @@ -42,6 +42,7 @@ import com.tangem.domain.walletmanager.WalletManagersFacade import com.tangem.domain.wallets.legacy.UserWalletsListManager import com.tangem.domain.wallets.repository.WalletsRepository import com.tangem.domain.wallets.usecase.GenerateWalletNameUseCase +import com.tangem.features.onboarding.v2.OnboardingV2FeatureToggles import com.tangem.features.onramp.OnrampFeatureToggles import com.tangem.tap.common.analytics.AnalyticsFactory import com.tangem.tap.common.analytics.api.AnalyticsHandlerBuilder @@ -176,6 +177,9 @@ abstract class TangemApplication : Application(), ImageLoaderFactory { private val onrampFeatureToggles: OnrampFeatureToggles get() = entryPoint.getOnrampFeatureToggles() + + private val onboardingV2FeatureToggles: OnboardingV2FeatureToggles + get() = entryPoint.getOnboardingV2FeatureToggles() // endregion override fun onCreate() { @@ -253,6 +257,7 @@ abstract class TangemApplication : Application(), ImageLoaderFactory { getUserCountryUseCase = getUserCountryUseCase, onrampFeatureToggles = onrampFeatureToggles, environmentConfigStorage = environmentConfigStorage, + onboardingV2FeatureToggles = onboardingV2FeatureToggles, ), ), ) diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt b/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt index 35007254c1..2f18b059ca 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt @@ -66,6 +66,10 @@ object OnboardingHelper { } fun whereToNavigate(scanResponse: ScanResponse): AppRoute { + if (store.inject(DaggerGraphState::onboardingV2FeatureToggles).isOnboardingV2Enabled) { + return AppRoute.Onboarding(scanResponse) + } + return when (val type = scanResponse.productType) { ProductType.Note -> AppRoute.OnboardingNote ProductType.Wallet, diff --git a/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphState.kt b/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphState.kt index fe6a63998e..075686701f 100644 --- a/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphState.kt +++ b/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphState.kt @@ -25,6 +25,7 @@ import com.tangem.domain.walletmanager.WalletManagersFacade import com.tangem.domain.wallets.legacy.UserWalletsListManager import com.tangem.domain.wallets.repository.WalletsRepository import com.tangem.domain.wallets.usecase.GenerateWalletNameUseCase +import com.tangem.features.onboarding.v2.OnboardingV2FeatureToggles import com.tangem.features.onramp.OnrampFeatureToggles import com.tangem.features.pushnotifications.api.navigation.PushNotificationsRouter import com.tangem.features.staking.api.navigation.StakingRouter @@ -69,4 +70,5 @@ data class DaggerGraphState( val getUserCountryUseCase: GetUserCountryUseCase? = null, val onrampFeatureToggles: OnrampFeatureToggles? = null, val environmentConfigStorage: EnvironmentConfigStorage? = null, + val onboardingV2FeatureToggles: OnboardingV2FeatureToggles? = null, ) : StateType \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/routing/utils/ChildFactory.kt b/app/src/main/java/com/tangem/tap/routing/utils/ChildFactory.kt index 5731dab42f..b031d03e46 100644 --- a/app/src/main/java/com/tangem/tap/routing/utils/ChildFactory.kt +++ b/app/src/main/java/com/tangem/tap/routing/utils/ChildFactory.kt @@ -12,6 +12,7 @@ import com.tangem.features.managetokens.component.ManageTokensComponent import com.tangem.features.managetokens.component.ManageTokensSource import com.tangem.features.markets.details.MarketsTokenDetailsComponent import com.tangem.features.onramp.component.BuyCryptoComponent +import com.tangem.features.onboarding.v2.entry.OnboardingEntryComponent import com.tangem.features.onramp.component.OnrampComponent import com.tangem.features.onramp.component.SellCryptoComponent import com.tangem.features.pushnotifications.api.navigation.PushNotificationsRouter @@ -52,6 +53,7 @@ internal class ChildFactory @Inject constructor( private val onrampComponentFactory: OnrampComponent.Factory, private val buyCryptoComponentFactory: BuyCryptoComponent.Factory, private val sellCryptoComponentFactory: SellCryptoComponent.Factory, + private val onboardingEntryComponentFactory: OnboardingEntryComponent.Factory, private val sendRouter: SendRouter, private val tokenDetailsRouter: TokenDetailsRouter, private val walletRouter: WalletRouter, @@ -213,6 +215,13 @@ internal class ChildFactory @Inject constructor( componentFactory = sellCryptoComponentFactory, ) } + is AppRoute.Onboarding -> { + route.asComponentChild( + contextProvider = contextProvider(route, contextFactory), + params = OnboardingEntryComponent.Params(route.scanResponse), + componentFactory = onboardingEntryComponentFactory, + ) + } } } diff --git a/common/routing/src/main/kotlin/com/tangem/common/routing/AppRoute.kt b/common/routing/src/main/kotlin/com/tangem/common/routing/AppRoute.kt index cb8be40a85..6744b6c0be 100644 --- a/common/routing/src/main/kotlin/com/tangem/common/routing/AppRoute.kt +++ b/common/routing/src/main/kotlin/com/tangem/common/routing/AppRoute.kt @@ -7,6 +7,7 @@ import com.tangem.common.routing.entity.SerializableIntent import com.tangem.core.decompose.navigation.Route import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.markets.TokenMarketParams +import com.tangem.domain.models.scan.ScanResponse import com.tangem.domain.qrscanning.models.SourceType import com.tangem.domain.staking.model.stakekit.Yield import com.tangem.domain.tokens.model.CryptoCurrency @@ -297,4 +298,7 @@ sealed class AppRoute(val path: String) : Route { data class SellCrypto( val userWalletId: UserWalletId, ) : AppRoute(path = "/sell_crypto/${userWalletId.stringValue}") + + // Onboarding V2 + data class Onboarding(val scanResponse: ScanResponse) : AppRoute(path = "/onboarding_v2") } \ No newline at end of file diff --git a/features/onboarding-v2/api/build.gradle.kts b/features/onboarding-v2/api/build.gradle.kts index d6e9adffe8..92e55d627f 100644 --- a/features/onboarding-v2/api/build.gradle.kts +++ b/features/onboarding-v2/api/build.gradle.kts @@ -11,7 +11,7 @@ android { dependencies { /* Project - Domain */ - implementation(projects.domain.wallets.models) + implementation(projects.domain.models) /* Project - Core */ implementation(projects.core.decompose) diff --git a/features/onboarding-v2/api/src/main/kotlin/com/tangem/features/onboarding/v2/OnboardingV2FeatureToggles.kt b/features/onboarding-v2/api/src/main/kotlin/com/tangem/features/onboarding/v2/OnboardingV2FeatureToggles.kt index af4b3152e0..e86227d668 100644 --- a/features/onboarding-v2/api/src/main/kotlin/com/tangem/features/onboarding/v2/OnboardingV2FeatureToggles.kt +++ b/features/onboarding-v2/api/src/main/kotlin/com/tangem/features/onboarding/v2/OnboardingV2FeatureToggles.kt @@ -1,5 +1,5 @@ package com.tangem.features.onboarding.v2 interface OnboardingV2FeatureToggles { - val isRewrittenOnboardingCodeInUse: Boolean + val isOnboardingV2Enabled: Boolean } \ No newline at end of file diff --git a/features/onboarding-v2/api/src/main/kotlin/com/tangem/features/onboarding/v2/entry/OnboardingEntryComponent.kt b/features/onboarding-v2/api/src/main/kotlin/com/tangem/features/onboarding/v2/entry/OnboardingEntryComponent.kt index 93b12a1d21..847e64541e 100644 --- a/features/onboarding-v2/api/src/main/kotlin/com/tangem/features/onboarding/v2/entry/OnboardingEntryComponent.kt +++ b/features/onboarding-v2/api/src/main/kotlin/com/tangem/features/onboarding/v2/entry/OnboardingEntryComponent.kt @@ -2,11 +2,12 @@ package com.tangem.features.onboarding.v2.entry import com.tangem.core.decompose.factory.ComponentFactory import com.tangem.core.ui.decompose.ComposableContentComponent +import com.tangem.domain.models.scan.ScanResponse interface OnboardingEntryComponent : ComposableContentComponent { data class Params( - val todo: String, + val scanResponse: ScanResponse, ) interface Factory : ComponentFactory diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/DefaultOnboardingV2FeatureToggles.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/DefaultOnboardingV2FeatureToggles.kt index 4245576b3e..fe67d84f57 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/DefaultOnboardingV2FeatureToggles.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/DefaultOnboardingV2FeatureToggles.kt @@ -6,6 +6,6 @@ import javax.inject.Inject internal class DefaultOnboardingV2FeatureToggles @Inject constructor( private val featureTogglesManager: FeatureTogglesManager, ) : OnboardingV2FeatureToggles { - override val isRewrittenOnboardingCodeInUse: Boolean + override val isOnboardingV2Enabled: Boolean get() = featureTogglesManager.isFeatureEnabled("ONBOARDING_CODE_REFACTORING_ENABLED") } \ No newline at end of file diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/entry/impl/DefaultOnboardingEntryComponent.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/entry/impl/DefaultOnboardingEntryComponent.kt new file mode 100644 index 0000000000..df2a3fbde4 --- /dev/null +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/entry/impl/DefaultOnboardingEntryComponent.kt @@ -0,0 +1,51 @@ +package com.tangem.features.onboarding.v2.entry.impl + +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import com.tangem.core.decompose.context.AppComponentContext +import com.tangem.core.decompose.context.child +import com.tangem.core.ui.extensions.stringReference +import com.tangem.features.onboarding.v2.entry.OnboardingEntryComponent +import com.tangem.features.onboarding.v2.entry.impl.ui.OnboardingEntry +import com.tangem.features.onboarding.v2.stepper.api.OnboardingStepperComponent +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject + +internal class DefaultOnboardingEntryComponent @AssistedInject constructor( + @Assisted val context: AppComponentContext, + @Assisted val params: OnboardingEntryComponent.Params, + stepperFactory: OnboardingStepperComponent.Factory, +) : OnboardingEntryComponent, AppComponentContext by context { + + private val stepperComponent = stepperFactory.create( + context = child("stepper"), + params = OnboardingStepperComponent.Params( + scanResponse = params.scanResponse, + initState = OnboardingStepperComponent.StepperState( + currentStep = 0, + steps = 3, + title = stringReference(""), + ), + popBack = { router.pop() }, + ), + ) + + @Composable + override fun Content(modifier: Modifier) { + OnboardingEntry( + modifier = modifier, + stepperContent = { modifierParam -> + stepperComponent.Content(modifierParam) + }, + ) + } + + @AssistedFactory + interface Factory : OnboardingEntryComponent.Factory { + override fun create( + context: AppComponentContext, + params: OnboardingEntryComponent.Params, + ): DefaultOnboardingEntryComponent + } +} \ No newline at end of file diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/entry/impl/di/ComponentModule.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/entry/impl/di/ComponentModule.kt new file mode 100644 index 0000000000..f24d33ef05 --- /dev/null +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/entry/impl/di/ComponentModule.kt @@ -0,0 +1,20 @@ +package com.tangem.features.onboarding.v2.entry.impl.di + +import com.tangem.features.onboarding.v2.entry.OnboardingEntryComponent +import com.tangem.features.onboarding.v2.entry.impl.DefaultOnboardingEntryComponent +import dagger.Binds +import dagger.Module +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +internal interface ComponentModule { + + @Binds + @Singleton + fun bindOnboardingEntryComponent( + factory: DefaultOnboardingEntryComponent.Factory, + ): OnboardingEntryComponent.Factory +} \ No newline at end of file diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/entry/impl/ui/OnboardingEntry.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/entry/impl/ui/OnboardingEntry.kt new file mode 100644 index 0000000000..5a17a8966e --- /dev/null +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/entry/impl/ui/OnboardingEntry.kt @@ -0,0 +1,44 @@ +package com.tangem.features.onboarding.v2.entry.impl.ui + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.statusBarsPadding +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.tooling.preview.Preview +import com.tangem.core.ui.components.appbar.TangemTopAppBar +import com.tangem.core.ui.components.appbar.models.TopAppBarButtonUM +import com.tangem.core.ui.res.TangemTheme +import com.tangem.core.ui.res.TangemThemePreview +import com.tangem.features.onboarding.v2.impl.R + +@Composable +internal inline fun OnboardingEntry(modifier: Modifier = Modifier, stepperContent: @Composable (Modifier) -> Unit) { + Column( + modifier = modifier + .fillMaxSize() + .statusBarsPadding() + .background(TangemTheme.colors.background.primary), + ) { + stepperContent(Modifier.fillMaxWidth()) + } +} + +@Preview +@Composable +private fun Preview() { + TangemThemePreview { + OnboardingEntry { + TangemTopAppBar( + title = "Onboarding", + startButton = TopAppBarButtonUM( + iconRes = R.drawable.ic_back_24, + enabled = true, + onIconClicked = {}, + ), + ) + } + } +} \ No newline at end of file diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/stepper/api/OnboardingStepperComponent.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/stepper/api/OnboardingStepperComponent.kt index 3a6cd92582..235ab31fc8 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/stepper/api/OnboardingStepperComponent.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/stepper/api/OnboardingStepperComponent.kt @@ -7,7 +7,7 @@ import com.tangem.core.ui.extensions.TextReference import com.tangem.domain.models.scan.ScanResponse import kotlinx.coroutines.flow.StateFlow -interface OnboardingStepperComponent : ComposableContentComponent { +internal interface OnboardingStepperComponent : ComposableContentComponent { data class StepperState( @IntRange(from = 0) val currentStep: Int, From 101e609dec43ee0d38244f84dde7fd2f0679b6fc Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 30 Oct 2024 13:13:53 +0400 Subject: [PATCH 046/106] Updated on 2026-08-14 --- features/onramp/impl/build.gradle.kts | 1 + .../onramp/buy/DefaultBuyCryptoComponent.kt | 37 ++------ .../features/onramp/entity/OnrampOperation.kt | 12 +++ .../DefaultOnrampOperationComponent.kt | 84 +++++++++++++++++++ .../DefaultOnrampSelectTokenComponent.kt | 46 ---------- ...mponent.kt => OnrampOperationComponent.kt} | 15 ++-- .../di/OnrampOperationComponentModule.kt | 20 +++++ .../di/OnrampSelectTokenComponentModule.kt | 20 ----- .../onramp/sell/DefaultSellCryptoComponent.kt | 34 ++------ .../tokenlist/OnrampTokenListComponent.kt | 9 +- .../tokenlist/model/OnrampTokenListModel.kt | 18 +++- 11 files changed, 157 insertions(+), 139 deletions(-) create mode 100644 features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/entity/OnrampOperation.kt create mode 100644 features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selecttoken/DefaultOnrampOperationComponent.kt delete mode 100644 features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selecttoken/DefaultOnrampSelectTokenComponent.kt rename features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selecttoken/{OnrampSelectTokenComponent.kt => OnrampOperationComponent.kt} (53%) create mode 100644 features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selecttoken/di/OnrampOperationComponentModule.kt delete mode 100644 features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selecttoken/di/OnrampSelectTokenComponentModule.kt diff --git a/features/onramp/impl/build.gradle.kts b/features/onramp/impl/build.gradle.kts index 042df5ae39..1ff3b4c39c 100644 --- a/features/onramp/impl/build.gradle.kts +++ b/features/onramp/impl/build.gradle.kts @@ -29,6 +29,7 @@ dependencies { implementation(projects.domain.balanceHiding) implementation(projects.domain.balanceHiding.models) implementation(projects.domain.legacy) + implementation(projects.domain.models) implementation(projects.domain.tokens) implementation(projects.domain.tokens.models) implementation(projects.domain.wallets) diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/buy/DefaultBuyCryptoComponent.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/buy/DefaultBuyCryptoComponent.kt index 1c463d0cdf..f8b1be4d51 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/buy/DefaultBuyCryptoComponent.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/buy/DefaultBuyCryptoComponent.kt @@ -3,39 +3,27 @@ package com.tangem.features.onramp.buy import androidx.compose.runtime.Composable import androidx.compose.runtime.Stable import androidx.compose.ui.Modifier -import arrow.core.getOrElse import com.tangem.core.decompose.context.AppComponentContext -import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase -import com.tangem.domain.appcurrency.model.AppCurrency -import com.tangem.domain.redux.ReduxStateHolder -import com.tangem.domain.tokens.legacy.TradeCryptoAction -import com.tangem.domain.tokens.model.CryptoCurrencyStatus -import com.tangem.domain.wallets.usecase.GetWalletsUseCase import com.tangem.features.onramp.component.BuyCryptoComponent -import com.tangem.features.onramp.impl.R -import com.tangem.features.onramp.selecttoken.OnrampSelectTokenComponent +import com.tangem.features.onramp.entity.OnrampOperation +import com.tangem.features.onramp.selecttoken.OnrampOperationComponent import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject -import kotlinx.coroutines.launch @Stable internal class DefaultBuyCryptoComponent @AssistedInject constructor( - onrampSelectTokenComponentFactory: OnrampSelectTokenComponent.Factory, + onrampOperationComponentFactory: OnrampOperationComponent.Factory, @Assisted private val appComponentContext: AppComponentContext, @Assisted private val params: BuyCryptoComponent.Params, - private val reduxStateHolder: ReduxStateHolder, - private val getWalletsUseCase: GetWalletsUseCase, - private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase, ) : BuyCryptoComponent { - private val selectTokenComponent: OnrampSelectTokenComponent = onrampSelectTokenComponentFactory.create( + private val selectTokenComponent: OnrampOperationComponent = onrampOperationComponentFactory.create( context = appComponentContext, - params = OnrampSelectTokenComponent.Params( + params = OnrampOperationComponent.Params( + operation = OnrampOperation.BUY, hasSearchBar = true, userWalletId = params.userWalletId, - titleResId = R.string.common_buy, - onTokenClick = ::onTokenClick, ), ) @@ -44,19 +32,6 @@ internal class DefaultBuyCryptoComponent @AssistedInject constructor( selectTokenComponent.Content(modifier = modifier) } - private fun onTokenClick(status: CryptoCurrencyStatus) { - appComponentContext.componentScope.launch { - reduxStateHolder.dispatch( - TradeCryptoAction.Buy( - userWallet = getWalletsUseCase.invokeSync().first { it.walletId == params.userWalletId }, - cryptoCurrencyStatus = status, - appCurrencyCode = getSelectedAppCurrencyUseCase.invokeSync() - .getOrElse { AppCurrency.Default }.code, - ), - ) - } - } - @AssistedFactory interface Factory : BuyCryptoComponent.Factory { diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/entity/OnrampOperation.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/entity/OnrampOperation.kt new file mode 100644 index 0000000000..1a94304325 --- /dev/null +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/entity/OnrampOperation.kt @@ -0,0 +1,12 @@ +package com.tangem.features.onramp.entity + +/** + * Onramp operation + * +[REDACTED_AUTHOR] + */ +internal enum class OnrampOperation { + BUY, + SELL, + ; +} \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selecttoken/DefaultOnrampOperationComponent.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selecttoken/DefaultOnrampOperationComponent.kt new file mode 100644 index 0000000000..429df40cb5 --- /dev/null +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selecttoken/DefaultOnrampOperationComponent.kt @@ -0,0 +1,84 @@ +package com.tangem.features.onramp.selecttoken + +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import arrow.core.getOrElse +import com.tangem.core.decompose.context.AppComponentContext +import com.tangem.core.decompose.context.child +import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase +import com.tangem.domain.appcurrency.model.AppCurrency +import com.tangem.domain.redux.ReduxStateHolder +import com.tangem.domain.tokens.legacy.TradeCryptoAction +import com.tangem.domain.tokens.model.CryptoCurrencyStatus +import com.tangem.domain.wallets.usecase.GetWalletsUseCase +import com.tangem.features.onramp.entity.OnrampOperation +import com.tangem.features.onramp.impl.R +import com.tangem.features.onramp.selecttoken.ui.OnrampSelectToken +import com.tangem.features.onramp.tokenlist.OnrampTokenListComponent +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject +import kotlinx.coroutines.launch + +internal class DefaultOnrampOperationComponent @AssistedInject constructor( + @Assisted appComponentContext: AppComponentContext, + onrampTokenListComponentFactory: OnrampTokenListComponent.Factory, + @Assisted private val params: OnrampOperationComponent.Params, + private val reduxStateHolder: ReduxStateHolder, + private val getWalletsUseCase: GetWalletsUseCase, + private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase, +) : AppComponentContext by appComponentContext, OnrampOperationComponent { + + private val onrampTokenListComponent: OnrampTokenListComponent = onrampTokenListComponentFactory.create( + context = child(key = "token_list"), + params = OnrampTokenListComponent.Params( + filterOperation = params.operation, + hasSearchBar = params.hasSearchBar, + userWalletId = params.userWalletId, + onTokenClick = ::onTokenClick, + ), + ) + + @Composable + override fun Content(modifier: Modifier) { + OnrampSelectToken( + titleResId = when (params.operation) { + OnrampOperation.BUY -> R.string.common_buy + OnrampOperation.SELL -> R.string.common_sell + }, + onBackClick = router::pop, + onrampTokenListComponent = onrampTokenListComponent, + modifier = modifier, + ) + } + + private fun onTokenClick(status: CryptoCurrencyStatus) { + componentScope.launch { + val appCurrencyCode = getSelectedAppCurrencyUseCase.invokeSync().getOrElse { AppCurrency.Default }.code + + reduxStateHolder.dispatch( + when (params.operation) { + OnrampOperation.BUY -> getBuyAction(status, appCurrencyCode) + OnrampOperation.SELL -> TradeCryptoAction.Sell(status, appCurrencyCode) + }, + ) + } + } + + private fun getBuyAction(status: CryptoCurrencyStatus, appCurrencyCode: String): TradeCryptoAction { + return TradeCryptoAction.Buy( + userWallet = getWalletsUseCase.invokeSync().first { it.walletId == params.userWalletId }, + cryptoCurrencyStatus = status, + appCurrencyCode = appCurrencyCode, + ) + } + + @AssistedFactory + interface Factory : OnrampOperationComponent.Factory { + + override fun create( + context: AppComponentContext, + params: OnrampOperationComponent.Params, + ): DefaultOnrampOperationComponent + } +} \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selecttoken/DefaultOnrampSelectTokenComponent.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selecttoken/DefaultOnrampSelectTokenComponent.kt deleted file mode 100644 index 50ec027209..0000000000 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selecttoken/DefaultOnrampSelectTokenComponent.kt +++ /dev/null @@ -1,46 +0,0 @@ -package com.tangem.features.onramp.selecttoken - -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import com.tangem.core.decompose.context.AppComponentContext -import com.tangem.core.decompose.context.child -import com.tangem.features.onramp.selecttoken.ui.OnrampSelectToken -import com.tangem.features.onramp.tokenlist.OnrampTokenListComponent -import dagger.assisted.Assisted -import dagger.assisted.AssistedFactory -import dagger.assisted.AssistedInject - -internal class DefaultOnrampSelectTokenComponent @AssistedInject constructor( - @Assisted appComponentContext: AppComponentContext, - onrampTokenListComponentFactory: OnrampTokenListComponent.Factory, - @Assisted private val params: OnrampSelectTokenComponent.Params, -) : AppComponentContext by appComponentContext, OnrampSelectTokenComponent { - - private val onrampTokenListComponent: OnrampTokenListComponent = onrampTokenListComponentFactory.create( - context = child(key = "token_list"), - params = OnrampTokenListComponent.Params( - hasSearchBar = params.hasSearchBar, - userWalletId = params.userWalletId, - onTokenClick = params.onTokenClick, - ), - ) - - @Composable - override fun Content(modifier: Modifier) { - OnrampSelectToken( - titleResId = params.titleResId, - onBackClick = router::pop, - onrampTokenListComponent = onrampTokenListComponent, - modifier = modifier, - ) - } - - @AssistedFactory - interface Factory : OnrampSelectTokenComponent.Factory { - - override fun create( - context: AppComponentContext, - params: OnrampSelectTokenComponent.Params, - ): DefaultOnrampSelectTokenComponent - } -} \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selecttoken/OnrampSelectTokenComponent.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selecttoken/OnrampOperationComponent.kt similarity index 53% rename from features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selecttoken/OnrampSelectTokenComponent.kt rename to features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selecttoken/OnrampOperationComponent.kt index 69aa520462..3f6b4fc489 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selecttoken/OnrampSelectTokenComponent.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selecttoken/OnrampOperationComponent.kt @@ -1,32 +1,29 @@ package com.tangem.features.onramp.selecttoken -import androidx.annotation.StringRes import com.tangem.core.decompose.factory.ComponentFactory import com.tangem.core.ui.decompose.ComposableContentComponent -import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.domain.wallets.models.UserWalletId +import com.tangem.features.onramp.entity.OnrampOperation /** - * Select token component + * Base operation component * [REDACTED_AUTHOR] */ -interface OnrampSelectTokenComponent : ComposableContentComponent { +internal interface OnrampOperationComponent : ComposableContentComponent { - interface Factory : ComponentFactory + interface Factory : ComponentFactory /** * Params * + * @property operation operation * @property hasSearchBar flag that indicates if search bar should be shown * @property userWalletId id of multi-currency wallet - * @property titleResId resource id of title - * @property onTokenClick callback for token click */ data class Params( + val operation: OnrampOperation, val hasSearchBar: Boolean, val userWalletId: UserWalletId, - @StringRes val titleResId: Int, - val onTokenClick: (CryptoCurrencyStatus) -> Unit, ) } \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selecttoken/di/OnrampOperationComponentModule.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selecttoken/di/OnrampOperationComponentModule.kt new file mode 100644 index 0000000000..b15e75f360 --- /dev/null +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selecttoken/di/OnrampOperationComponentModule.kt @@ -0,0 +1,20 @@ +package com.tangem.features.onramp.selecttoken.di + +import com.tangem.features.onramp.selecttoken.DefaultOnrampOperationComponent +import com.tangem.features.onramp.selecttoken.OnrampOperationComponent +import dagger.Binds +import dagger.Module +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +internal interface OnrampOperationComponentModule { + + @Binds + @Singleton + fun bindOnrampOperationComponentFactory( + factory: DefaultOnrampOperationComponent.Factory, + ): OnrampOperationComponent.Factory +} \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selecttoken/di/OnrampSelectTokenComponentModule.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selecttoken/di/OnrampSelectTokenComponentModule.kt deleted file mode 100644 index 01bece010d..0000000000 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selecttoken/di/OnrampSelectTokenComponentModule.kt +++ /dev/null @@ -1,20 +0,0 @@ -package com.tangem.features.onramp.selecttoken.di - -import com.tangem.features.onramp.selecttoken.DefaultOnrampSelectTokenComponent -import com.tangem.features.onramp.selecttoken.OnrampSelectTokenComponent -import dagger.Binds -import dagger.Module -import dagger.hilt.InstallIn -import dagger.hilt.components.SingletonComponent -import javax.inject.Singleton - -@Module -@InstallIn(SingletonComponent::class) -internal interface OnrampSelectTokenComponentModule { - - @Binds - @Singleton - fun bindOnrampSelectTokenComponentFactory( - factory: DefaultOnrampSelectTokenComponent.Factory, - ): OnrampSelectTokenComponent.Factory -} \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/sell/DefaultSellCryptoComponent.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/sell/DefaultSellCryptoComponent.kt index b1a3589e6c..ffba48a738 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/sell/DefaultSellCryptoComponent.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/sell/DefaultSellCryptoComponent.kt @@ -3,37 +3,27 @@ package com.tangem.features.onramp.sell import androidx.compose.runtime.Composable import androidx.compose.runtime.Stable import androidx.compose.ui.Modifier -import arrow.core.getOrElse import com.tangem.core.decompose.context.AppComponentContext -import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase -import com.tangem.domain.appcurrency.model.AppCurrency -import com.tangem.domain.redux.ReduxStateHolder -import com.tangem.domain.tokens.legacy.TradeCryptoAction -import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.features.onramp.component.SellCryptoComponent -import com.tangem.features.onramp.impl.R -import com.tangem.features.onramp.selecttoken.OnrampSelectTokenComponent +import com.tangem.features.onramp.entity.OnrampOperation +import com.tangem.features.onramp.selecttoken.OnrampOperationComponent import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject -import kotlinx.coroutines.launch @Stable internal class DefaultSellCryptoComponent @AssistedInject constructor( - onrampSelectTokenComponentFactory: OnrampSelectTokenComponent.Factory, + onrampOperationComponentFactory: OnrampOperationComponent.Factory, @Assisted private val appComponentContext: AppComponentContext, @Assisted private val params: SellCryptoComponent.Params, - private val reduxStateHolder: ReduxStateHolder, - private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase, ) : SellCryptoComponent { - private val selectTokenComponent: OnrampSelectTokenComponent = onrampSelectTokenComponentFactory.create( + private val selectTokenComponent: OnrampOperationComponent = onrampOperationComponentFactory.create( context = appComponentContext, - params = OnrampSelectTokenComponent.Params( + params = OnrampOperationComponent.Params( + operation = OnrampOperation.SELL, hasSearchBar = true, userWalletId = params.userWalletId, - titleResId = R.string.common_sell, - onTokenClick = ::onTokenClick, ), ) @@ -42,18 +32,6 @@ internal class DefaultSellCryptoComponent @AssistedInject constructor( selectTokenComponent.Content(modifier = modifier) } - private fun onTokenClick(status: CryptoCurrencyStatus) { - appComponentContext.componentScope.launch { - reduxStateHolder.dispatch( - TradeCryptoAction.Sell( - cryptoCurrencyStatus = status, - appCurrencyCode = getSelectedAppCurrencyUseCase.invokeSync() - .getOrElse { AppCurrency.Default }.code, - ), - ) - } - } - @AssistedFactory interface Factory : SellCryptoComponent.Factory { diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/OnrampTokenListComponent.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/OnrampTokenListComponent.kt index 8b924c893e..bcb5ab848a 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/OnrampTokenListComponent.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/OnrampTokenListComponent.kt @@ -7,6 +7,7 @@ import androidx.compose.ui.Modifier import com.tangem.core.decompose.factory.ComponentFactory import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.domain.wallets.models.UserWalletId +import com.tangem.features.onramp.entity.OnrampOperation /** Token list component that present list of token for multi-currency wallet */ @Stable @@ -21,11 +22,13 @@ internal interface OnrampTokenListComponent { /** * Params * - * @property hasSearchBar flag that indicates if search bar should be shown - * @property userWalletId id of multi-currency wallet - * @property onTokenClick callback for token click + * @property filterOperation operation that is used to filter tokens by availability + * @property hasSearchBar flag that indicates if search bar should be shown + * @property userWalletId id of multi-currency wallet + * @property onTokenClick callback for token click */ data class Params( + val filterOperation: OnrampOperation, val hasSearchBar: Boolean, val userWalletId: UserWalletId, val onTokenClick: (CryptoCurrencyStatus) -> Unit, diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/model/OnrampTokenListModel.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/model/OnrampTokenListModel.kt index efbb04ac25..fc6329296f 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/model/OnrampTokenListModel.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/model/OnrampTokenListModel.kt @@ -8,9 +8,12 @@ import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase import com.tangem.domain.core.utils.getOrElse +import com.tangem.domain.exchange.RampStateManager import com.tangem.domain.tokens.GetTokenListUseCase import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.domain.tokens.model.TokenList +import com.tangem.domain.wallets.usecase.GetWalletsUseCase +import com.tangem.features.onramp.entity.OnrampOperation import com.tangem.features.onramp.tokenlist.OnrampTokenListComponent import com.tangem.features.onramp.tokenlist.entity.TokenListUM import com.tangem.features.onramp.tokenlist.entity.TokenListUMController @@ -32,11 +35,16 @@ internal class OnrampTokenListModel @Inject constructor( private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase, private val getBalanceHidingSettingsUseCase: GetBalanceHidingSettingsUseCase, private val getTokenListUseCase: GetTokenListUseCase, + private val getWalletsUseCase: GetWalletsUseCase, + private val rampStateManager: RampStateManager, ) : Model() { val state: StateFlow = tokenListUMController.state private val params: OnrampTokenListComponent.Params = paramsContainer.require() + private val scanResponse by lazy { + getWalletsUseCase.invokeSync().first { it.walletId == params.userWalletId }.scanResponse + } init { subscribeOnUpdateState() @@ -98,8 +106,14 @@ internal class OnrampTokenListModel @Inject constructor( } } - // TODO: [REDACTED_JIRA] private fun List.filterByAvailability(): List { - return this + return filter { + when (params.filterOperation) { + OnrampOperation.BUY -> { + rampStateManager.availableForBuy(scanResponse = scanResponse, cryptoCurrency = it.currency) + } + OnrampOperation.SELL -> rampStateManager.availableForSell(cryptoCurrency = it.currency) + } + } } } \ No newline at end of file From 4791ce3ca59b183bf9c5e45eb67652bdc77fe2c6 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 30 Oct 2024 14:11:51 +0500 Subject: [PATCH 047/106] Updated on 2026-08-14 --- .../com/tangem/data/transaction/DefaultTransactionRepository.kt | 2 ++ .../state/recipient/SendRecipientMemoFieldConverter.kt | 1 + gradle/dependencies.toml | 2 +- .../src/main/java/com/tangem/blockchainsdk/utils/Blockchain.kt | 2 -- 4 files changed, 4 insertions(+), 3 deletions(-) diff --git a/data/transaction/src/main/java/com/tangem/data/transaction/DefaultTransactionRepository.kt b/data/transaction/src/main/java/com/tangem/data/transaction/DefaultTransactionRepository.kt index f627d8a830..84f0b8020d 100644 --- a/data/transaction/src/main/java/com/tangem/data/transaction/DefaultTransactionRepository.kt +++ b/data/transaction/src/main/java/com/tangem/data/transaction/DefaultTransactionRepository.kt @@ -3,6 +3,7 @@ package com.tangem.data.transaction import androidx.core.text.isDigitsOnly import com.tangem.blockchain.blockchains.algorand.AlgorandTransactionExtras import com.tangem.blockchain.blockchains.binance.BinanceTransactionExtras +import com.tangem.blockchain.blockchains.casper.CasperTransactionExtras import com.tangem.blockchain.blockchains.cosmos.CosmosTransactionExtras import com.tangem.blockchain.blockchains.ethereum.EthereumTransactionExtras import com.tangem.blockchain.blockchains.hedera.HederaTransactionExtras @@ -274,6 +275,7 @@ internal class DefaultTransactionRepository( Blockchain.Hedera -> HederaTransactionExtras(memo) Blockchain.Algorand -> AlgorandTransactionExtras(memo) Blockchain.InternetComputer -> memo.toLongOrNull()?.let { ICPTransactionExtras(it) } + Blockchain.Casper -> memo.toLongOrNull()?.let { CasperTransactionExtras(it) } else -> null } } diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/recipient/SendRecipientMemoFieldConverter.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/recipient/SendRecipientMemoFieldConverter.kt index 5c9e9b0679..5b59bbb297 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/recipient/SendRecipientMemoFieldConverter.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/recipient/SendRecipientMemoFieldConverter.kt @@ -38,6 +38,7 @@ internal class SendRecipientMemoFieldConverter( Blockchain.Algorand.id, Blockchain.Sei.id, Blockchain.InternetComputer.id, + Blockchain.Casper.id, -> { convert( value = Data(memo = memo, label = R.string.send_extras_hint_memo), diff --git a/gradle/dependencies.toml b/gradle/dependencies.toml index a907455383..adda9b2c34 100644 --- a/gradle/dependencies.toml +++ b/gradle/dependencies.toml @@ -88,7 +88,7 @@ markdownComposeView = "0.5.4" # endregion Other libraries # region Tangem -tangemBlockchainSdk = "develop-841" +tangemBlockchainSdk = "develop-843" #tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds tangemCardSdk = "develop-391" #tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^ diff --git a/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/utils/Blockchain.kt b/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/utils/Blockchain.kt index 736a97fa6c..3132bfa610 100644 --- a/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/utils/Blockchain.kt +++ b/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/utils/Blockchain.kt @@ -405,6 +405,4 @@ private val excludedBlockchains = listOf( Blockchain.Unknown, Blockchain.Nexa, Blockchain.NexaTestnet, - Blockchain.Casper, - Blockchain.CasperTestnet, ) \ No newline at end of file From ea9faf69923881425c2c19f3908b11d9f78851d7 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 30 Oct 2024 12:37:29 +0200 Subject: [PATCH 048/106] Updated on 2026-08-14 --- .../tangem/data/staking/DefaultStakingErrorResolver.kt | 4 ++-- .../domain/staking/analytics/StakingAnalyticsEvent.kt | 9 +++++++++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/data/staking/src/main/java/com/tangem/data/staking/DefaultStakingErrorResolver.kt b/data/staking/src/main/java/com/tangem/data/staking/DefaultStakingErrorResolver.kt index 34ebad7aa5..a5f5c43f33 100644 --- a/data/staking/src/main/java/com/tangem/data/staking/DefaultStakingErrorResolver.kt +++ b/data/staking/src/main/java/com/tangem/data/staking/DefaultStakingErrorResolver.kt @@ -26,8 +26,8 @@ internal class DefaultStakingErrorResolver( is StakingError.StakeKitUnknownError -> { analyticsEventHandler.send(StakingAnalyticsEvent.StakeKitApiUnknownError(error)) } - else -> { - // intentionally do nothing + is StakingError.DomainError -> { + analyticsEventHandler.send(StakingAnalyticsEvent.DomainError(error)) } } diff --git a/domain/staking/src/main/java/com/tangem/domain/staking/analytics/StakingAnalyticsEvent.kt b/domain/staking/src/main/java/com/tangem/domain/staking/analytics/StakingAnalyticsEvent.kt index dbe640d0b0..063e0e9f70 100644 --- a/domain/staking/src/main/java/com/tangem/domain/staking/analytics/StakingAnalyticsEvent.kt +++ b/domain/staking/src/main/java/com/tangem/domain/staking/analytics/StakingAnalyticsEvent.kt @@ -136,6 +136,15 @@ sealed class StakingAnalyticsEvent( }, ) + data class DomainError( + val stakeKitDomainError: StakingError.DomainError, + ) : StakingAnalyticsEvent( + event = "App Errors", + params = buildMap { + addIfValueIsNotNull(AnalyticsParam.ERROR_DESCRIPTION, stakeKitDomainError.message) + }, + ) + fun MutableMap.addIfValueIsNotNull(key: String, value: Any?) { if (value != null) { put(key, value.toString()) From 2afe7db48e5073b5e3084201415b061cde3286e3 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 30 Oct 2024 15:44:50 +0500 Subject: [PATCH 049/106] Updated on 2026-08-14 --- .../impl/presentation/state/StakingUiState.kt | 1 + .../helpers/StakingFeeTransactionLoader.kt | 40 +++++-------------- .../ConfirmationStatePreviewData.kt | 1 + .../SetButtonsStateTransformer.kt | 9 ++++- .../SetConfirmationStateInitTransformer.kt | 3 ++ .../SetUpdatedAllowanceTransformer.kt | 17 ++++++++ .../viewmodel/StakingViewModel.kt | 30 ++++++++++++-- 7 files changed, 65 insertions(+), 36 deletions(-) create mode 100644 features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/confirmation/SetUpdatedAllowanceTransformer.kt diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/StakingUiState.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/StakingUiState.kt index cdc4967df2..0613d78e25 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/StakingUiState.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/StakingUiState.kt @@ -118,6 +118,7 @@ internal sealed class StakingStates { val footerText: TextReference, val transactionDoneState: TransactionDoneState, val isApprovalNeeded: Boolean, + val allowance: BigDecimal, val reduceAmountBy: BigDecimal?, ) : ConfirmationState() diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/helpers/StakingFeeTransactionLoader.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/helpers/StakingFeeTransactionLoader.kt index a56aa88c94..207436cf09 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/helpers/StakingFeeTransactionLoader.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/helpers/StakingFeeTransactionLoader.kt @@ -8,7 +8,6 @@ import com.tangem.blockchain.common.transaction.TransactionFee import com.tangem.common.extensions.isZero import com.tangem.common.ui.amountScreen.models.AmountState import com.tangem.domain.staking.EstimateGasUseCase -import com.tangem.domain.staking.model.StakingApproval import com.tangem.domain.staking.model.stakekit.PendingAction import com.tangem.domain.staking.model.stakekit.StakingError import com.tangem.domain.staking.model.stakekit.Yield @@ -17,7 +16,6 @@ import com.tangem.domain.staking.model.stakekit.transaction.ActionParams import com.tangem.domain.staking.model.stakekit.transaction.StakingGasEstimate import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.domain.transaction.error.GetFeeError -import com.tangem.domain.transaction.usecase.GetAllowanceUseCase import com.tangem.domain.transaction.usecase.GetFeeUseCase import com.tangem.domain.wallets.models.UserWallet import com.tangem.features.staking.impl.presentation.state.StakingStateController @@ -37,13 +35,11 @@ import java.math.BigDecimal @Suppress("LongParameterList") internal class StakingFeeTransactionLoader @AssistedInject constructor( private val stateController: StakingStateController, - private val getAllowanceUseCase: GetAllowanceUseCase, private val getFeeUseCase: GetFeeUseCase, private val estimateGasUseCase: EstimateGasUseCase, @Assisted private val cryptoCurrencyStatus: CryptoCurrencyStatus, @Assisted private val userWallet: UserWallet, @Assisted private val yield: Yield, - @Assisted private val stakingApproval: StakingApproval, ) { suspend fun getFee( @@ -66,31 +62,16 @@ internal class StakingFeeTransactionLoader @AssistedInject constructor( val validatorAddress = validatorState.chosenValidator.address - val approval = stakingApproval as? StakingApproval.Needed - if (approval != null && state.actionType == StakingActionCommonType.Enter) { - val allowance = getAllowanceUseCase( - userWalletId = userWallet.walletId, - cryptoCurrency = cryptoCurrencyStatus.currency, - spenderAddress = approval.spenderAddress, - ).getOrElse { BigDecimal.ZERO } - - if (allowance < amount) { - getApproveFee( - amount = amount, - validatorAddress = validatorAddress, - onApprovalFee = onApprovalFee, - onApprovalFeeError = onFeeError, - ) - } else { - estimateGas( - pendingAction = pendingAction, - pendingActions = pendingActions, - amount = amount, - validatorAddress = validatorAddress, - onStakingFeeError = onStakingFeeError, - onStakingFee = onStakingFee, - ) - } + val isEnter = state.actionType == StakingActionCommonType.Enter + val isApprovalNeeded = confirmationState.isApprovalNeeded + val isAllowanceNotEnough = confirmationState.allowance < amount + if (isEnter && isApprovalNeeded && isAllowanceNotEnough) { + getApproveFee( + amount = amount, + validatorAddress = validatorAddress, + onApprovalFee = onApprovalFee, + onApprovalFeeError = onFeeError, + ) } else { estimateGas( pendingAction = pendingAction, @@ -234,7 +215,6 @@ internal class StakingFeeTransactionLoader @AssistedInject constructor( cryptoCurrencyStatus: CryptoCurrencyStatus, userWallet: UserWallet, yield: Yield, - stakingApproval: StakingApproval, ): StakingFeeTransactionLoader } } \ No newline at end of file diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/previewdata/ConfirmationStatePreviewData.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/previewdata/ConfirmationStatePreviewData.kt index dbcf70f6bb..ac86e7084a 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/previewdata/ConfirmationStatePreviewData.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/previewdata/ConfirmationStatePreviewData.kt @@ -45,6 +45,7 @@ internal object ConfirmationStatePreviewData { transactionDoneState = TransactionDoneState.Empty, pendingAction = null, isApprovalNeeded = false, + allowance = BigDecimal.ZERO, reduceAmountBy = null, pendingActions = null, ) diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetButtonsStateTransformer.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetButtonsStateTransformer.kt index c2761a0713..c2fcce7f80 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetButtonsStateTransformer.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetButtonsStateTransformer.kt @@ -1,5 +1,6 @@ package com.tangem.features.staking.impl.presentation.state.transformers +import com.tangem.common.ui.amountScreen.models.AmountState import com.tangem.common.ui.navigationButtons.NavigationButton import com.tangem.common.ui.navigationButtons.NavigationButtonsState import com.tangem.core.navigation.url.UrlOpener @@ -9,6 +10,7 @@ import com.tangem.core.ui.extensions.resourceReference import com.tangem.domain.staking.model.stakekit.action.StakingActionCommonType import com.tangem.features.staking.impl.presentation.state.* import com.tangem.features.staking.impl.presentation.state.utils.getPendingActionTitle +import com.tangem.utils.extensions.orZero import com.tangem.utils.transformer.Transformer import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf @@ -116,13 +118,16 @@ internal class SetButtonsStateTransformer( } private fun StakingUiState.getConfirmationButtonText(): TextReference { - return if (confirmationState is StakingStates.ConfirmationState.Data) { + val confirmationState = confirmationState as? StakingStates.ConfirmationState.Data + val amountState = amountState as? AmountState.Data + return if (confirmationState != null && amountState != null) { if (confirmationState.innerState == InnerConfirmationStakingState.COMPLETED) { resourceReference(R.string.common_close) } else { when (actionType) { StakingActionCommonType.Enter -> { - if (confirmationState.isApprovalNeeded) { + val amount = amountState.amountTextField.cryptoAmount.value.orZero() + if (confirmationState.isApprovalNeeded && confirmationState.allowance < amount) { resourceReference(R.string.give_permission_title) } else { resourceReference(R.string.common_stake) diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetConfirmationStateInitTransformer.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetConfirmationStateInitTransformer.kt index 8387933908..e638ea2e20 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetConfirmationStateInitTransformer.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetConfirmationStateInitTransformer.kt @@ -12,10 +12,12 @@ import com.tangem.features.staking.impl.presentation.state.utils.isTronStakedBal import com.tangem.utils.transformer.Transformer import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf +import java.math.BigDecimal internal class SetConfirmationStateInitTransformer( private val isEnter: Boolean, private val stakingApproval: StakingApproval, + private val stakingAllowance: BigDecimal, private val cryptoCurrencyStatus: CryptoCurrencyStatus, private val pendingActions: ImmutableList? = null, private val pendingAction: PendingAction? = pendingActions?.firstOrNull(), @@ -60,6 +62,7 @@ internal class SetConfirmationStateInitTransformer( footerText = TextReference.EMPTY, transactionDoneState = TransactionDoneState.Empty, isApprovalNeeded = stakingApproval is StakingApproval.Needed, + allowance = stakingAllowance, reduceAmountBy = null, pendingAction = pendingAction, pendingActions = pendingActions.takeIf { isComposePendingActions }, diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/confirmation/SetUpdatedAllowanceTransformer.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/confirmation/SetUpdatedAllowanceTransformer.kt new file mode 100644 index 0000000000..5d8677de4f --- /dev/null +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/confirmation/SetUpdatedAllowanceTransformer.kt @@ -0,0 +1,17 @@ +package com.tangem.features.staking.impl.presentation.state.transformers.confirmation + +import com.tangem.features.staking.impl.presentation.state.StakingStates +import com.tangem.features.staking.impl.presentation.state.StakingUiState +import com.tangem.utils.transformer.Transformer +import java.math.BigDecimal + +internal class SetUpdatedAllowanceTransformer( + private val allowance: BigDecimal, +) : Transformer { + override fun transform(prevState: StakingUiState): StakingUiState { + val confirmationState = prevState.confirmationState as? StakingStates.ConfirmationState.Data + return prevState.copy( + confirmationState = confirmationState?.copy(allowance = allowance) ?: prevState.confirmationState, + ) + } +} \ No newline at end of file diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/viewmodel/StakingViewModel.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/viewmodel/StakingViewModel.kt index 159ba0ac57..998261a393 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/viewmodel/StakingViewModel.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/viewmodel/StakingViewModel.kt @@ -26,7 +26,10 @@ import com.tangem.domain.feedback.SaveBlockchainErrorUseCase import com.tangem.domain.feedback.SendFeedbackEmailUseCase import com.tangem.domain.feedback.models.BlockchainErrorInfo import com.tangem.domain.feedback.models.FeedbackEmailType -import com.tangem.domain.staking.* +import com.tangem.domain.staking.GetActionsUseCase +import com.tangem.domain.staking.InvalidatePendingTransactionsUseCase +import com.tangem.domain.staking.IsAnyTokenStakedUseCase +import com.tangem.domain.staking.IsApproveNeededUseCase import com.tangem.domain.staking.model.StakingApproval import com.tangem.domain.staking.model.stakekit.BalanceItem import com.tangem.domain.staking.model.stakekit.PendingAction @@ -63,6 +66,7 @@ import com.tangem.features.staking.impl.presentation.state.helpers.StakingTransa import com.tangem.features.staking.impl.presentation.state.transformers.* import com.tangem.features.staking.impl.presentation.state.transformers.amount.* import com.tangem.features.staking.impl.presentation.state.transformers.approval.* +import com.tangem.features.staking.impl.presentation.state.transformers.confirmation.SetUpdatedAllowanceTransformer import com.tangem.features.staking.impl.presentation.state.transformers.notifications.AddStakingNotificationsTransformer import com.tangem.features.staking.impl.presentation.state.transformers.notifications.DismissStakingNotificationsStateTransformer import com.tangem.features.staking.impl.presentation.state.transformers.validator.ValidatorSelectChangeTransformer @@ -167,7 +171,6 @@ internal class StakingViewModel @Inject constructor( cryptoCurrencyStatus = cryptoCurrencyStatus, userWallet = userWallet, yield = yield, - stakingApproval = stakingApproval, ) } @@ -192,6 +195,7 @@ internal class StakingViewModel @Inject constructor( ) private var stakingApproval: StakingApproval = StakingApproval.Empty + private var stakingAllowance: BigDecimal = BigDecimal.ZERO private var isAmountSubtractAvailable: Boolean = false private var isAnyTokenStaked: Boolean = false private val allowanceTaskScheduler = SingleTaskScheduler() @@ -357,6 +361,7 @@ internal class StakingViewModel @Inject constructor( isEnter = true, cryptoCurrencyStatus = cryptoCurrencyStatus, stakingApproval = stakingApproval, + stakingAllowance = stakingAllowance, ), ValidatorSelectChangeTransformer( selectedValidator = null, @@ -651,9 +656,11 @@ internal class StakingViewModel @Inject constructor( } }, onSuccess = { allowance -> + stakingAllowance = allowance val amount = (value.amountState as? AmountState.Data)?.amountTextField?.cryptoAmount?.value ?: error("No amount provided") if (allowance >= amount) { + stateController.update(SetUpdatedAllowanceTransformer(allowance)) getFee() allowanceTaskScheduler.cancelTask() } @@ -730,8 +737,22 @@ internal class StakingViewModel @Inject constructor( this.stakingStateRouter = stateRouter } - private fun setupApprovalNeeded() { - stakingApproval = isApproveNeededUseCase(cryptoCurrencyStatus.currency).getOrElse { StakingApproval.Empty } + private suspend fun setupApprovalNeeded() { + stakingApproval = isApproveNeededUseCase(cryptoCurrencyStatus.currency).fold( + ifRight = { approval -> + if (approval is StakingApproval.Needed) { + stakingAllowance = getAllowanceUseCase( + userWalletId = userWalletId, + cryptoCurrency = cryptoCurrencyStatus.currency, + spenderAddress = approval.spenderAddress, + ).getOrElse { BigDecimal.ZERO } + } + approval + }, + ifLeft = { + StakingApproval.Empty + }, + ) } private suspend fun setupIsAnyTokenStaked() { @@ -886,6 +907,7 @@ internal class StakingViewModel @Inject constructor( stakingApproval = stakingApproval, pendingActions = pendingActions, pendingAction = pendingAction, + stakingAllowance = stakingAllowance, ), ValidatorSelectChangeTransformer( selectedValidator = validator, From 54eca8e5140ef1807a0de57379417e35191ab740 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 30 Oct 2024 15:41:46 +0300 Subject: [PATCH 050/106] Updated on 2026-08-14 --- .../navigation/inner/InnerNavigation.kt | 16 ++++++ .../navigation/inner/InnerNavigationHolder.kt | 5 ++ .../impl/DefaultOnboardingEntryComponent.kt | 53 ++++++++++++++++++- .../entry/impl/model/OnboardingEntryModel.kt | 23 ++++++++ .../entry/impl/model/state/OnboardingState.kt | 7 +++ .../impl/routing/OnboardingChildFactory.kt | 21 ++++++++ .../v2/entry/impl/routing/OnboardingRoute.kt | 9 ++++ .../v2/entry/impl/ui/OnboardingEntry.kt | 38 +++++++------ .../stepper/api/OnboardingStepperComponent.kt | 1 + .../v2/stepper/impl/ui/OnboardingStepper.kt | 15 ++++++ .../api/OnboardingWallet12Component.kt | 13 +++++ .../DefaultOnboardingWallet12Component.kt | 46 ++++++++++++++++ .../v2/wallet12/impl/di/ComponentModule.kt | 20 +++++++ 13 files changed, 250 insertions(+), 17 deletions(-) create mode 100644 core/decompose/src/main/kotlin/com/tangem/core/decompose/navigation/inner/InnerNavigation.kt create mode 100644 core/decompose/src/main/kotlin/com/tangem/core/decompose/navigation/inner/InnerNavigationHolder.kt create mode 100644 features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/entry/impl/model/OnboardingEntryModel.kt create mode 100644 features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/entry/impl/model/state/OnboardingState.kt create mode 100644 features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/entry/impl/routing/OnboardingChildFactory.kt create mode 100644 features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/entry/impl/routing/OnboardingRoute.kt create mode 100644 features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/wallet12/api/OnboardingWallet12Component.kt create mode 100644 features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/wallet12/impl/DefaultOnboardingWallet12Component.kt create mode 100644 features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/wallet12/impl/di/ComponentModule.kt diff --git a/core/decompose/src/main/kotlin/com/tangem/core/decompose/navigation/inner/InnerNavigation.kt b/core/decompose/src/main/kotlin/com/tangem/core/decompose/navigation/inner/InnerNavigation.kt new file mode 100644 index 0000000000..eecc01d57f --- /dev/null +++ b/core/decompose/src/main/kotlin/com/tangem/core/decompose/navigation/inner/InnerNavigation.kt @@ -0,0 +1,16 @@ +package com.tangem.core.decompose.navigation.inner + +import kotlinx.coroutines.flow.StateFlow + +/** + * Interface to provide internal navigation access from child to parent + */ +interface InnerNavigation { + val state: StateFlow + fun pop(onComplete: (Boolean) -> Unit) +} + +interface InnerNavigationState { + val stackSize: Int + val stackMaxSize: Int? +} \ No newline at end of file diff --git a/core/decompose/src/main/kotlin/com/tangem/core/decompose/navigation/inner/InnerNavigationHolder.kt b/core/decompose/src/main/kotlin/com/tangem/core/decompose/navigation/inner/InnerNavigationHolder.kt new file mode 100644 index 0000000000..3c60bfb7b7 --- /dev/null +++ b/core/decompose/src/main/kotlin/com/tangem/core/decompose/navigation/inner/InnerNavigationHolder.kt @@ -0,0 +1,5 @@ +package com.tangem.core.decompose.navigation.inner + +interface InnerNavigationHolder { + val innerNavigation: InnerNavigation +} \ No newline at end of file diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/entry/impl/DefaultOnboardingEntryComponent.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/entry/impl/DefaultOnboardingEntryComponent.kt index df2a3fbde4..4766803dd8 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/entry/impl/DefaultOnboardingEntryComponent.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/entry/impl/DefaultOnboardingEntryComponent.kt @@ -2,10 +2,21 @@ package com.tangem.features.onboarding.v2.entry.impl import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier +import com.arkivanov.decompose.router.stack.ChildStack +import com.arkivanov.decompose.router.stack.StackNavigation +import com.arkivanov.decompose.router.stack.childStack +import com.arkivanov.decompose.router.stack.pop +import com.arkivanov.decompose.value.Value import com.tangem.core.decompose.context.AppComponentContext import com.tangem.core.decompose.context.child +import com.tangem.core.decompose.context.childByContext +import com.tangem.core.decompose.model.getOrCreateModel +import com.tangem.core.decompose.navigation.inner.InnerNavigationHolder import com.tangem.core.ui.extensions.stringReference import com.tangem.features.onboarding.v2.entry.OnboardingEntryComponent +import com.tangem.features.onboarding.v2.entry.impl.model.OnboardingEntryModel +import com.tangem.features.onboarding.v2.entry.impl.routing.OnboardingChildFactory +import com.tangem.features.onboarding.v2.entry.impl.routing.OnboardingRoute import com.tangem.features.onboarding.v2.entry.impl.ui.OnboardingEntry import com.tangem.features.onboarding.v2.stepper.api.OnboardingStepperComponent import dagger.assisted.Assisted @@ -16,21 +27,56 @@ internal class DefaultOnboardingEntryComponent @AssistedInject constructor( @Assisted val context: AppComponentContext, @Assisted val params: OnboardingEntryComponent.Params, stepperFactory: OnboardingStepperComponent.Factory, + onboardingChildFactory: OnboardingChildFactory, ) : OnboardingEntryComponent, AppComponentContext by context { + private val model: OnboardingEntryModel = getOrCreateModel(params) + private val stepperComponent = stepperFactory.create( context = child("stepper"), params = OnboardingStepperComponent.Params( scanResponse = params.scanResponse, initState = OnboardingStepperComponent.StepperState( currentStep = 0, - steps = 3, + steps = 0, title = stringReference(""), + showProgress = false, ), - popBack = { router.pop() }, + popBack = { + popInternal { success -> + if (success.not()) { + stackNavigation.pop() + } + } + }, ), ) + private val stackNavigation = StackNavigation() + + private val innerStack: Value> = childStack( + key = "innerStack", + source = stackNavigation, + serializer = null, + initialConfiguration = model.state.value.currentRoute, + handleBackButton = true, + childFactory = { configuration, factoryContext -> + onboardingChildFactory.createChild( + route = configuration, + childContext = childByContext(factoryContext), + ) + }, + ) + + private fun popInternal(onComplete: (Boolean) -> Unit) { + val activeChild = innerStack.value.active.instance + if (activeChild is InnerNavigationHolder) { + activeChild.innerNavigation.pop(onComplete) + } else { + onComplete(false) + } + } + @Composable override fun Content(modifier: Modifier) { OnboardingEntry( @@ -38,6 +84,9 @@ internal class DefaultOnboardingEntryComponent @AssistedInject constructor( stepperContent = { modifierParam -> stepperComponent.Content(modifierParam) }, + content = { modifierParam -> + // TODO + }, ) } diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/entry/impl/model/OnboardingEntryModel.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/entry/impl/model/OnboardingEntryModel.kt new file mode 100644 index 0000000000..8c4d8cb474 --- /dev/null +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/entry/impl/model/OnboardingEntryModel.kt @@ -0,0 +1,23 @@ +package com.tangem.features.onboarding.v2.entry.impl.model + +import com.tangem.core.decompose.di.ComponentScoped +import com.tangem.core.decompose.model.Model +import com.tangem.features.onboarding.v2.entry.OnboardingEntryComponent +import com.tangem.features.onboarding.v2.entry.impl.model.state.OnboardingState +import com.tangem.features.onboarding.v2.entry.impl.routing.OnboardingRoute +import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import kotlinx.coroutines.flow.MutableStateFlow +import javax.inject.Inject + +@ComponentScoped +internal class OnboardingEntryModel @Inject constructor( + override val dispatchers: CoroutineDispatcherProvider, + private val params: OnboardingEntryComponent.Params, +) : Model() { + + val state = MutableStateFlow( + OnboardingState( + currentRoute = OnboardingRoute.Wallet12(params.scanResponse), + ), + ) +} \ No newline at end of file diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/entry/impl/model/state/OnboardingState.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/entry/impl/model/state/OnboardingState.kt new file mode 100644 index 0000000000..66e5d7c4c4 --- /dev/null +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/entry/impl/model/state/OnboardingState.kt @@ -0,0 +1,7 @@ +package com.tangem.features.onboarding.v2.entry.impl.model.state + +import com.tangem.features.onboarding.v2.entry.impl.routing.OnboardingRoute + +internal data class OnboardingState( + val currentRoute: OnboardingRoute, +) \ No newline at end of file diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/entry/impl/routing/OnboardingChildFactory.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/entry/impl/routing/OnboardingChildFactory.kt new file mode 100644 index 0000000000..faf659c52e --- /dev/null +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/entry/impl/routing/OnboardingChildFactory.kt @@ -0,0 +1,21 @@ +package com.tangem.features.onboarding.v2.entry.impl.routing + +import com.tangem.core.decompose.context.AppComponentContext +import com.tangem.features.onboarding.v2.wallet12.api.OnboardingWallet12Component +import javax.inject.Inject + +internal class OnboardingChildFactory @Inject constructor( + private val wallet12ComponentFactory: OnboardingWallet12Component.Factory, +) { + + fun createChild(route: OnboardingRoute, childContext: AppComponentContext) { + when (route) { + is OnboardingRoute.Wallet12 -> wallet12ComponentFactory.create( + context = childContext, + params = OnboardingWallet12Component.Params( + scanResponse = route.scanResponse, + ), + ) + } + } +} \ No newline at end of file diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/entry/impl/routing/OnboardingRoute.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/entry/impl/routing/OnboardingRoute.kt new file mode 100644 index 0000000000..5b9847190a --- /dev/null +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/entry/impl/routing/OnboardingRoute.kt @@ -0,0 +1,9 @@ +package com.tangem.features.onboarding.v2.entry.impl.routing + +import com.tangem.core.decompose.navigation.Route +import com.tangem.domain.models.scan.ScanResponse + +sealed class OnboardingRoute : Route { + + data class Wallet12(val scanResponse: ScanResponse) : OnboardingRoute() +} \ No newline at end of file diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/entry/impl/ui/OnboardingEntry.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/entry/impl/ui/OnboardingEntry.kt index 5a17a8966e..3b8da8d889 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/entry/impl/ui/OnboardingEntry.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/entry/impl/ui/OnboardingEntry.kt @@ -1,12 +1,10 @@ package com.tangem.features.onboarding.v2.entry.impl.ui import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.statusBarsPadding +import androidx.compose.foundation.layout.* import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color import androidx.compose.ui.tooling.preview.Preview import com.tangem.core.ui.components.appbar.TangemTopAppBar import com.tangem.core.ui.components.appbar.models.TopAppBarButtonUM @@ -15,7 +13,11 @@ import com.tangem.core.ui.res.TangemThemePreview import com.tangem.features.onboarding.v2.impl.R @Composable -internal inline fun OnboardingEntry(modifier: Modifier = Modifier, stepperContent: @Composable (Modifier) -> Unit) { +internal inline fun OnboardingEntry( + modifier: Modifier = Modifier, + stepperContent: @Composable (Modifier) -> Unit, + content: @Composable (Modifier) -> Unit, +) { Column( modifier = modifier .fillMaxSize() @@ -23,6 +25,7 @@ internal inline fun OnboardingEntry(modifier: Modifier = Modifier, stepperConten .background(TangemTheme.colors.background.primary), ) { stepperContent(Modifier.fillMaxWidth()) + content(Modifier.fillMaxSize()) } } @@ -30,15 +33,20 @@ internal inline fun OnboardingEntry(modifier: Modifier = Modifier, stepperConten @Composable private fun Preview() { TangemThemePreview { - OnboardingEntry { - TangemTopAppBar( - title = "Onboarding", - startButton = TopAppBarButtonUM( - iconRes = R.drawable.ic_back_24, - enabled = true, - onIconClicked = {}, - ), - ) - } + OnboardingEntry( + stepperContent = { + TangemTopAppBar( + title = "Onboarding", + startButton = TopAppBarButtonUM( + iconRes = R.drawable.ic_back_24, + enabled = true, + onIconClicked = {}, + ), + ) + }, + content = { modifier -> + Box(modifier.background(Color.Cyan.copy(alpha = 0.3f))) + }, + ) } } \ No newline at end of file diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/stepper/api/OnboardingStepperComponent.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/stepper/api/OnboardingStepperComponent.kt index 235ab31fc8..328deab736 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/stepper/api/OnboardingStepperComponent.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/stepper/api/OnboardingStepperComponent.kt @@ -13,6 +13,7 @@ internal interface OnboardingStepperComponent : ComposableContentComponent { @IntRange(from = 0) val currentStep: Int, @IntRange(from = 0) val steps: Int, val title: TextReference, + val showProgress: Boolean, ) class Params( diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/stepper/impl/ui/OnboardingStepper.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/stepper/impl/ui/OnboardingStepper.kt index a4d735e82b..a22c50812e 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/stepper/impl/ui/OnboardingStepper.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/stepper/impl/ui/OnboardingStepper.kt @@ -1,11 +1,15 @@ package com.tangem.features.onboarding.v2.stepper.impl.ui +import androidx.compose.animation.core.LinearEasing +import androidx.compose.animation.core.animateFloatAsState +import androidx.compose.animation.core.tween import androidx.compose.foundation.background import androidx.compose.foundation.layout.* import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.alpha import androidx.compose.ui.graphics.StrokeCap import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp @@ -29,6 +33,15 @@ internal fun OnboardingStepper( val fraction = state.currentStep.toFloat() / state.steps.coerceAtLeast(1) val animatedIndicatorFraction by TangemAnimations.horizontalIndicatorAsState(targetFraction = fraction) + val progressAlpha by animateFloatAsState( + targetValue = if (state.showProgress) 1f else 0f, + animationSpec = tween( + durationMillis = 300, + easing = LinearEasing, + ), + label = "progressAlpha", + ) + Column( modifier = modifier, verticalArrangement = Arrangement.spacedBy(8.dp), @@ -48,6 +61,7 @@ internal fun OnboardingStepper( LinearProgressIndicator( modifier = Modifier + .alpha(progressAlpha) .padding(horizontal = 16.dp) .height(4.dp) .fillMaxWidth(), @@ -74,6 +88,7 @@ private fun Preview() { currentStep = 2, steps = 3, title = resourceReference(R.string.common_done), + showProgress = true, ), onBackClick = {}, onSupportButtonClick = {}, diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/wallet12/api/OnboardingWallet12Component.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/wallet12/api/OnboardingWallet12Component.kt new file mode 100644 index 0000000000..c950955961 --- /dev/null +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/wallet12/api/OnboardingWallet12Component.kt @@ -0,0 +1,13 @@ +package com.tangem.features.onboarding.v2.wallet12.api + +import com.tangem.core.decompose.factory.ComponentFactory +import com.tangem.core.decompose.navigation.inner.InnerNavigationHolder +import com.tangem.core.ui.decompose.ComposableContentComponent +import com.tangem.domain.models.scan.ScanResponse + +interface OnboardingWallet12Component : ComposableContentComponent, InnerNavigationHolder { + + data class Params(val scanResponse: ScanResponse) + + interface Factory : ComponentFactory +} \ No newline at end of file diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/wallet12/impl/DefaultOnboardingWallet12Component.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/wallet12/impl/DefaultOnboardingWallet12Component.kt new file mode 100644 index 0000000000..edfbb70eed --- /dev/null +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/wallet12/impl/DefaultOnboardingWallet12Component.kt @@ -0,0 +1,46 @@ +package com.tangem.features.onboarding.v2.wallet12.impl + +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import com.tangem.core.decompose.context.AppComponentContext +import com.tangem.core.decompose.navigation.inner.InnerNavigation +import com.tangem.core.decompose.navigation.inner.InnerNavigationState +import com.tangem.features.onboarding.v2.wallet12.api.OnboardingWallet12Component +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject +import kotlinx.coroutines.flow.MutableStateFlow + +@Suppress("UnusedPrivateMember") +class DefaultOnboardingWallet12Component @AssistedInject constructor( + @Assisted private val context: AppComponentContext, + @Assisted private val params: OnboardingWallet12Component.Params, +) : OnboardingWallet12Component, AppComponentContext by context { + + override val innerNavigation: InnerNavigation = object : InnerNavigation { + override val state = MutableStateFlow( + Wallet12InnerNavigationState(0, null), // TODO + ) + + override fun pop(onComplete: (Boolean) -> Unit) { + // TODO + } + } + + @Composable + override fun Content(modifier: Modifier) { + } + + @AssistedFactory + interface Factory : OnboardingWallet12Component.Factory { + override fun create( + context: AppComponentContext, + params: OnboardingWallet12Component.Params, + ): DefaultOnboardingWallet12Component + } +} + +data class Wallet12InnerNavigationState( + override val stackSize: Int, + override val stackMaxSize: Int?, +) : InnerNavigationState \ No newline at end of file diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/wallet12/impl/di/ComponentModule.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/wallet12/impl/di/ComponentModule.kt new file mode 100644 index 0000000000..70322dc3b1 --- /dev/null +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/wallet12/impl/di/ComponentModule.kt @@ -0,0 +1,20 @@ +package com.tangem.features.onboarding.v2.wallet12.impl.di + +import com.tangem.features.onboarding.v2.wallet12.api.OnboardingWallet12Component +import com.tangem.features.onboarding.v2.wallet12.impl.DefaultOnboardingWallet12Component +import dagger.Binds +import dagger.Module +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +internal interface ComponentModule { + + @Binds + @Singleton + fun bindOnboardingWallet12Component( + factory: DefaultOnboardingWallet12Component.Factory, + ): OnboardingWallet12Component.Factory +} \ No newline at end of file From 50aa66a44ad5917976d58b3f412bc04537b85e41 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 30 Oct 2024 17:53:45 +0400 Subject: [PATCH 051/106] Updated on 2026-08-14 --- .../tangem/data/managetokens/DefaultManageTokensRepository.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/manage-tokens/src/main/kotlin/com/tangem/data/managetokens/DefaultManageTokensRepository.kt b/data/manage-tokens/src/main/kotlin/com/tangem/data/managetokens/DefaultManageTokensRepository.kt index 70c7c69396..52f7501447 100644 --- a/data/manage-tokens/src/main/kotlin/com/tangem/data/managetokens/DefaultManageTokensRepository.kt +++ b/data/manage-tokens/src/main/kotlin/com/tangem/data/managetokens/DefaultManageTokensRepository.kt @@ -109,7 +109,7 @@ internal class DefaultManageTokensRepository( ), active = true, searchText = query, - offset = request.offset * request.limit, + offset = request.offset + request.limit, limit = request.limit, ).getOrThrow() } From 9b1c4483e30c90aab48e4f7c987f9292151f1c17 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 30 Oct 2024 19:16:12 +0300 Subject: [PATCH 052/106] Updated on 2026-08-14 --- gradle/dependencies.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/dependencies.toml b/gradle/dependencies.toml index adda9b2c34..afdee0331b 100644 --- a/gradle/dependencies.toml +++ b/gradle/dependencies.toml @@ -90,7 +90,7 @@ markdownComposeView = "0.5.4" # region Tangem tangemBlockchainSdk = "develop-843" #tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds -tangemCardSdk = "develop-391" +tangemCardSdk = "develop-394" #tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^ tangemVico = "2.0.0-alpha.25-tangem17" #tangemVico = "0.0.1" # Keep it! - used for local builds ^ From 0db08ff57129adfb6d1a9e4187bc002ba22f7779 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 30 Oct 2024 20:55:33 +0300 Subject: [PATCH 053/106] Updated on 2026-08-14 --- gradle/dependencies.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/dependencies.toml b/gradle/dependencies.toml index afdee0331b..34eb2d3fc5 100644 --- a/gradle/dependencies.toml +++ b/gradle/dependencies.toml @@ -90,7 +90,7 @@ markdownComposeView = "0.5.4" # region Tangem tangemBlockchainSdk = "develop-843" #tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds -tangemCardSdk = "develop-394" +tangemCardSdk = "develop-395" #tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^ tangemVico = "2.0.0-alpha.25-tangem17" #tangemVico = "0.0.1" # Keep it! - used for local builds ^ From a48967b3cee4aa8e0f202c17d50cd951468fcbb7 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 30 Oct 2024 22:55:21 +0500 Subject: [PATCH 054/106] Updated on 2026-08-14 --- .../core/ui/extensions/BlockchainIcons.kt | 5 +++ core/ui/src/main/res/drawable/ic_xodex_22.xml | 9 ++++++ .../ui/src/main/res/drawable/img_xodex_22.xml | 31 +++++++++++++++++++ .../feature/swap/domain/SwapInteractorImpl.kt | 1 + gradle/dependencies.toml | 2 +- .../tangem/blockchainsdk/utils/Blockchain.kt | 3 ++ 6 files changed, 50 insertions(+), 1 deletion(-) create mode 100644 core/ui/src/main/res/drawable/ic_xodex_22.xml create mode 100644 core/ui/src/main/res/drawable/img_xodex_22.xml diff --git a/core/ui/src/main/java/com/tangem/core/ui/extensions/BlockchainIcons.kt b/core/ui/src/main/java/com/tangem/core/ui/extensions/BlockchainIcons.kt index 1270dd2741..ec74fa9c24 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/extensions/BlockchainIcons.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/extensions/BlockchainIcons.kt @@ -80,6 +80,7 @@ fun getActiveIconRes(blockchainId: String): Int { "energy-web-x", "energy-web-x/test" -> R.drawable.img_energy_web_22 "core", "core/test" -> R.drawable.img_core_22 "casper-network", "casper-network/test" -> R.drawable.img_casper_22 + "xodex" -> R.drawable.img_xodex_22 else -> R.drawable.ic_alert_24 } } @@ -161,6 +162,7 @@ fun getActiveIconResByNetworkId(networkId: String): Int { "energy-web-x", "energy-web-x/test" -> R.drawable.img_energy_web_22 "core", "core/test" -> R.drawable.img_core_22 "casper-network", "casper-network/test" -> R.drawable.img_casper_22 + "xodex" -> R.drawable.img_xodex_22 else -> R.drawable.ic_alert_24 } } @@ -239,6 +241,7 @@ fun getActiveIconResByCoinId(coinId: String): Int { "energy-web-x", "energy-web-x/test" -> R.drawable.img_energy_web_22 "core", "core/test" -> R.drawable.img_core_22 "casper-network" -> R.drawable.img_casper_22 + "xodex" -> R.drawable.img_xodex_22 else -> R.drawable.ic_alert_24 } } @@ -320,6 +323,7 @@ fun getGreyedOutIconRes(blockchainId: String): Int { "energy-web-x", "energy-web-x/test" -> R.drawable.ic_energy_web_22 "core", "core/test" -> R.drawable.ic_core_22 "casper-network", "casper-network/test" -> R.drawable.ic_casper_22 + "xodex" -> R.drawable.ic_xodex_22 else -> R.drawable.ic_alert_24 } } @@ -401,6 +405,7 @@ fun getGreyedOutIconResByNetworkId(networkId: String): Int { "energy-web-x", "energy-web-x/test" -> R.drawable.ic_energy_web_22 "core", "core/test" -> R.drawable.ic_core_22 "casper-network", "casper-network/test" -> R.drawable.ic_casper_22 + "xodex" -> R.drawable.ic_xodex_22 else -> R.drawable.ic_alert_24 } } \ No newline at end of file diff --git a/core/ui/src/main/res/drawable/ic_xodex_22.xml b/core/ui/src/main/res/drawable/ic_xodex_22.xml new file mode 100644 index 0000000000..bb8c9d880d --- /dev/null +++ b/core/ui/src/main/res/drawable/ic_xodex_22.xml @@ -0,0 +1,9 @@ + + + diff --git a/core/ui/src/main/res/drawable/img_xodex_22.xml b/core/ui/src/main/res/drawable/img_xodex_22.xml new file mode 100644 index 0000000000..878bf9187f --- /dev/null +++ b/core/ui/src/main/res/drawable/img_xodex_22.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt index 63b9c43970..59e27766ec 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt @@ -1045,6 +1045,7 @@ internal class SwapInteractorImpl @AssistedInject constructor( Blockchain.CasperTestnet, Blockchain.Core, Blockchain.CoreTestnet, + Blockchain.Xodex, -> Fee.Common(feeAmount) // endregion } diff --git a/gradle/dependencies.toml b/gradle/dependencies.toml index 34eb2d3fc5..427f9a46be 100644 --- a/gradle/dependencies.toml +++ b/gradle/dependencies.toml @@ -88,7 +88,7 @@ markdownComposeView = "0.5.4" # endregion Other libraries # region Tangem -tangemBlockchainSdk = "develop-843" +tangemBlockchainSdk = "develop-844" #tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds tangemCardSdk = "develop-395" #tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^ diff --git a/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/utils/Blockchain.kt b/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/utils/Blockchain.kt index 3132bfa610..2ae1e364a2 100644 --- a/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/utils/Blockchain.kt +++ b/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/utils/Blockchain.kt @@ -138,6 +138,7 @@ fun Blockchain.Companion.fromNetworkId(networkId: String): Blockchain? { "core/test" -> Blockchain.CoreTestnet "casper-network" -> Blockchain.Casper "casper-network/test" -> Blockchain.CasperTestnet + "xodex" -> Blockchain.Xodex else -> null } } @@ -275,6 +276,7 @@ fun Blockchain.toNetworkId(): String { Blockchain.CasperTestnet -> "casper-network/test" Blockchain.Core -> "core" Blockchain.CoreTestnet -> "core/test" + Blockchain.Xodex -> "xodex" } } @@ -364,6 +366,7 @@ fun Blockchain.toCoinId(): String { Blockchain.EnergyWebX, Blockchain.EnergyWebXTestnet -> "energy-web-token" Blockchain.Casper, Blockchain.CasperTestnet -> "casper-network" Blockchain.Core, Blockchain.CoreTestnet -> "coredaoorg" + Blockchain.Xodex -> "xodex" } } From 4a59f8899002936cf4f8a15ae7ccc42e6665a857 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 30 Oct 2024 23:55:53 +0500 Subject: [PATCH 055/106] Updated on 2026-08-14 --- .../com/tangem/tap/common/extensions/Store.kt | 4 ++-- .../com/tangem/tap/domain/TapWalletManager.kt | 10 +++------- .../analytics/TokenScreenAnalyticsEvent.kt | 5 ----- .../viewmodels/TokenDetailsViewModel.kt | 2 -- .../wallet/analytics/PortfolioEvent.kt | 17 ----------------- .../analytics/WalletScreenAnalyticsEvent.kt | 4 ---- .../viewmodels/intents/WalletClickIntents.kt | 5 ----- .../intents/WalletContentClickIntents.kt | 6 ------ 8 files changed, 5 insertions(+), 48 deletions(-) delete mode 100644 features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/analytics/PortfolioEvent.kt diff --git a/app/src/main/java/com/tangem/tap/common/extensions/Store.kt b/app/src/main/java/com/tangem/tap/common/extensions/Store.kt index 27fc3c3bc3..b86d13b6e9 100644 --- a/app/src/main/java/com/tangem/tap/common/extensions/Store.kt +++ b/app/src/main/java/com/tangem/tap/common/extensions/Store.kt @@ -43,8 +43,8 @@ fun Store<*>.dispatchNotification(resId: Int) { dispatchOnMain(GlobalAction.ShowNotification(resId)) } -suspend fun Store.onUserWalletSelected(userWallet: UserWallet, sendAnalyticsEvent: Boolean = false) { - state.globalState.tapWalletManager.onWalletSelected(userWallet, sendAnalyticsEvent) +suspend fun Store.onUserWalletSelected(userWallet: UserWallet) { + state.globalState.tapWalletManager.onWalletSelected(userWallet) } fun Store<*>.dispatchErrorNotification(error: TapError) { diff --git a/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt b/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt index faaa017b70..4a7fac7d10 100644 --- a/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt @@ -3,7 +3,6 @@ package com.tangem.tap.domain import com.tangem.blockchain.common.Token import com.tangem.blockchain.common.Wallet import com.tangem.core.analytics.Analytics -import com.tangem.core.analytics.models.Basic import com.tangem.domain.common.extensions.withMainContext import com.tangem.domain.wallets.models.UserWallet import com.tangem.operations.attestation.Attestation @@ -28,19 +27,16 @@ class TapWalletManager( field = value } - suspend fun onWalletSelected(userWallet: UserWallet, sendAnalyticsEvent: Boolean) { + suspend fun onWalletSelected(userWallet: UserWallet) { // If a previous job was running, it gets cancelled before the new one starts, // ensuring that only one job is active at any given time. loadUserWalletDataJob = CoroutineScope(dispatchers.io) - .launch { loadUserWalletData(userWallet, sendAnalyticsEvent) } + .launch { loadUserWalletData(userWallet) } .also { it.join() } } - private suspend fun loadUserWalletData(userWallet: UserWallet, sendAnalyticsEvent: Boolean) { + private suspend fun loadUserWalletData(userWallet: UserWallet) { Analytics.setContext(userWallet.scanResponse) - if (sendAnalyticsEvent) { - Analytics.send(Basic.WalletOpened()) - } val scanResponse = userWallet.scanResponse val card = scanResponse.card val attestationFailed = card.attestation.status == Attestation.Status.Failed diff --git a/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/analytics/TokenScreenAnalyticsEvent.kt b/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/analytics/TokenScreenAnalyticsEvent.kt index bea218c179..c661b761bd 100644 --- a/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/analytics/TokenScreenAnalyticsEvent.kt +++ b/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/analytics/TokenScreenAnalyticsEvent.kt @@ -18,11 +18,6 @@ sealed class TokenScreenAnalyticsEvent( params = mapOf("Token" to token), ) - class Refreshed(token: String) : TokenScreenAnalyticsEvent( - event = "Refreshed", - params = mapOf("Token" to token), - ) - class ButtonRemoveToken(token: String) : TokenScreenAnalyticsEvent( "Button - Remove Token", params = mapOf("Token" to token), diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt index a63d88e92a..540eb5eb32 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt @@ -720,8 +720,6 @@ internal class TokenDetailsViewModel @Inject constructor( } override fun onRefreshSwipe(isRefreshing: Boolean) { - analyticsEventsHandler.send(TokenScreenAnalyticsEvent.Refreshed(cryptoCurrency.symbol)) - internalUiState.value = stateFactory.getRefreshingState() viewModelScope.launch(dispatchers.main) { diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/analytics/PortfolioEvent.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/analytics/PortfolioEvent.kt deleted file mode 100644 index 85553a1445..0000000000 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/analytics/PortfolioEvent.kt +++ /dev/null @@ -1,17 +0,0 @@ -package com.tangem.feature.wallet.presentation.wallet.analytics - -import com.tangem.core.analytics.models.AnalyticsEvent - -sealed class PortfolioEvent( - event: String, - params: Map = mapOf(), -) : AnalyticsEvent("Portfolio", event, params) { - - object Refreshed : PortfolioEvent("Refreshed") - - object ButtonManageTokens : PortfolioEvent("Button - Manage Tokens") - - object TokenTapped : PortfolioEvent("Token is Tapped") - - object OrganizeTokens : PortfolioEvent("Button - Organize Tokens") -} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/analytics/WalletScreenAnalyticsEvent.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/analytics/WalletScreenAnalyticsEvent.kt index c0021f5d7e..9eadef3def 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/analytics/WalletScreenAnalyticsEvent.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/analytics/WalletScreenAnalyticsEvent.kt @@ -23,8 +23,6 @@ sealed class WalletScreenAnalyticsEvent { override val oneTimeEventId: String = id + userWalletId.stringValue } - data object WalletOpened : Basic(event = "Wallet Opened") - class CardWasScanned(source: AnalyticsParam.ScreensSources) : Basic( event = "Card Was Scanned", params = mapOf( @@ -74,7 +72,6 @@ sealed class WalletScreenAnalyticsEvent { ) : AnalyticsEvent(category = "Main Screen", event = event, params = params) { data object ScreenOpened : MainScreen(event = "Screen opened") - data object WalletSwipe : MainScreen(event = "Wallet Swipe") class EnableBiometrics(state: AnalyticsParam.OnOffState) : MainScreen( event = "Enable Biometric", @@ -88,7 +85,6 @@ sealed class WalletScreenAnalyticsEvent { data object NoticeBackupYourWalletTapped : MainScreen(event = "Notice - Backup Your Wallet Tapped") data object NoticeScanYourCardTapped : MainScreen(event = "Notice - Scan Your Card Tapped") - data object NoticeWalletLocked : MainScreen(event = "Notice - Wallet Locked") data object WalletUnlockTapped : MainScreen(event = "Notice - Wallet Unlock Tapped") data object NetworksUnreachable : MainScreen(event = "Notice - Networks Unreachable") diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletClickIntents.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletClickIntents.kt index a1ddf993ae..0c716472a9 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletClickIntents.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletClickIntents.kt @@ -1,6 +1,5 @@ package com.tangem.feature.wallet.presentation.wallet.viewmodels.intents -import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase import com.tangem.domain.appcurrency.extenstions.unwrap import com.tangem.domain.common.util.cardTypesResolver @@ -12,7 +11,6 @@ import com.tangem.domain.tokens.FetchTokenListUseCase.RefreshMode import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase import com.tangem.domain.wallets.usecase.SelectWalletUseCase import com.tangem.feature.wallet.presentation.router.InnerWalletRouter -import com.tangem.feature.wallet.presentation.wallet.analytics.PortfolioEvent import com.tangem.feature.wallet.presentation.wallet.domain.unwrap import com.tangem.feature.wallet.presentation.wallet.loaders.WalletScreenContentLoader import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController @@ -43,7 +41,6 @@ internal class WalletClickIntents @Inject constructor( private val fetchCurrencyStatusUseCase: FetchCurrencyStatusUseCase, private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase, private val neverToShowWalletsScrollPreview: NeverToShowWalletsScrollPreview, - private val analyticsEventHandler: AnalyticsEventHandler, private val dispatchers: CoroutineDispatcherProvider, ) : BaseWalletClickIntents(), WalletCardClickIntents by walletCardClickIntentsImplementor, @@ -87,13 +84,11 @@ internal class WalletClickIntents @Inject constructor( fun onRefreshSwipe(showRefreshState: Boolean) { when (stateHolder.getSelectedWallet()) { is WalletState.MultiCurrency.Content -> { - analyticsEventHandler.send(PortfolioEvent.Refreshed) refreshMultiCurrencyContent(showRefreshState) } is WalletState.SingleCurrency.Content, is WalletState.Visa.Content, -> { - analyticsEventHandler.send(PortfolioEvent.Refreshed) refreshSingleCurrencyContent(showRefreshState) } is WalletState.MultiCurrency.Locked, diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletContentClickIntents.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletContentClickIntents.kt index ba469eaf52..22c9441399 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletContentClickIntents.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletContentClickIntents.kt @@ -1,7 +1,6 @@ package com.tangem.feature.wallet.presentation.wallet.viewmodels.intents import arrow.core.getOrElse -import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.domain.redux.ReduxStateHolder import com.tangem.domain.settings.ShouldShowMarketsTooltipUseCase import com.tangem.domain.tokens.GetCryptoCurrencyActionsUseCase @@ -12,7 +11,6 @@ import com.tangem.domain.tokens.model.TokenActionsState import com.tangem.domain.txhistory.usecase.GetExplorerTransactionUrlUseCase import com.tangem.domain.wallets.models.UserWallet import com.tangem.domain.wallets.usecase.GetUserWalletUseCase -import com.tangem.feature.wallet.presentation.wallet.analytics.PortfolioEvent import com.tangem.feature.wallet.presentation.wallet.domain.unwrap import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController import com.tangem.feature.wallet.presentation.wallet.state.model.ActionsBottomSheetConfig @@ -56,7 +54,6 @@ internal class WalletContentClickIntentsImplementor @Inject constructor( private val getCryptoCurrencyActionsUseCase: GetCryptoCurrencyActionsUseCase, private val getExplorerTransactionUrlUseCase: GetExplorerTransactionUrlUseCase, private val shouldShowMarketsTooltipUseCase: ShouldShowMarketsTooltipUseCase, - private val analyticsEventHandler: AnalyticsEventHandler, private val dispatchers: CoroutineDispatcherProvider, private val reduxStateHolder: ReduxStateHolder, ) : BaseWalletClickIntents(), WalletContentClickIntents { @@ -92,13 +89,11 @@ internal class WalletContentClickIntentsImplementor @Inject constructor( } override fun onManageTokensClick() { - analyticsEventHandler.send(PortfolioEvent.ButtonManageTokens) reduxStateHolder.dispatch(action = TokensAction.SetArgs.ManageAccess) router.openManageTokensScreen(userWalletId = stateHolder.getSelectedWalletId()) } override fun onOrganizeTokensClick() { - analyticsEventHandler.send(PortfolioEvent.OrganizeTokens) router.openOrganizeTokensScreen(userWalletId = stateHolder.getSelectedWalletId()) } @@ -110,7 +105,6 @@ internal class WalletContentClickIntentsImplementor @Inject constructor( } override fun onTokenItemClick(currencyStatus: CryptoCurrencyStatus) { - analyticsEventHandler.send(PortfolioEvent.TokenTapped) router.openTokenDetails(stateHolder.getSelectedWalletId(), currencyStatus) } From a22f5b7ef8529237e286e8894836ec8099052056 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 31 Oct 2024 13:32:27 +0500 Subject: [PATCH 056/106] Updated on 2026-08-14 --- .../com/tangem/tap/common/analytics/events/Token.kt | 8 +++++++- .../tap/common/ui/AddressInfoBottomSheetDialog.kt | 2 +- .../model/analytics/TokenReceiveAnalyticsEvent.kt | 6 +++++- .../impl/analytics/MarketDetailsAnalyticsEvent.kt | 7 +++++++ .../details/impl/model/MarketsTokenDetailsModel.kt | 2 ++ .../portfolio/impl/model/MarketsPortfolioModel.kt | 1 - .../tokendetails/viewmodels/TokenDetailsViewModel.kt | 2 +- .../wallet/analytics/WalletScreenAnalyticsEvent.kt | 7 ++++++- .../wallet/analytics/utils/TokenListAnalyticsSender.kt | 10 +++++----- .../intents/WalletCurrencyActionsClickIntents.kt | 4 +++- 10 files changed, 37 insertions(+), 12 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/common/analytics/events/Token.kt b/app/src/main/java/com/tangem/tap/common/analytics/events/Token.kt index 56c286492d..bf0bdc539a 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/events/Token.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/events/Token.kt @@ -1,6 +1,7 @@ package com.tangem.tap.common.analytics.events import com.tangem.core.analytics.models.AnalyticsEvent +import com.tangem.core.analytics.models.AnalyticsParam.Key.TOKEN_PARAM /** [REDACTED_AUTHOR] @@ -17,7 +18,12 @@ sealed class Token( params: Map = mapOf(), ) : Token("Token / Receive", event, params) { - class ScreenOpened : Receive("Receive Screen Opened") + class ScreenOpened( + val token: String, + ) : Receive( + event = "Receive Screen Opened", + params = mapOf(TOKEN_PARAM to token), + ) class ButtonCopyAddress : Receive("Button - Copy Address") class ButtonShareAddress : Receive("Button - Share Address") } diff --git a/app/src/main/java/com/tangem/tap/common/ui/AddressInfoBottomSheetDialog.kt b/app/src/main/java/com/tangem/tap/common/ui/AddressInfoBottomSheetDialog.kt index 6a2a14d073..8b2cb093dd 100644 --- a/app/src/main/java/com/tangem/tap/common/ui/AddressInfoBottomSheetDialog.kt +++ b/app/src/main/java/com/tangem/tap/common/ui/AddressInfoBottomSheetDialog.kt @@ -38,7 +38,7 @@ internal class AddressInfoBottomSheetDialog( override fun show() { super.show() - Analytics.send(Token.Receive.ScreenOpened()) + Analytics.send(Token.Receive.ScreenOpened(stateDialog.currency.currencySymbol)) showData(data = stateDialog.addressData) } diff --git a/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/analytics/TokenReceiveAnalyticsEvent.kt b/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/analytics/TokenReceiveAnalyticsEvent.kt index f97493ffa2..45d43689a8 100644 --- a/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/analytics/TokenReceiveAnalyticsEvent.kt +++ b/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/analytics/TokenReceiveAnalyticsEvent.kt @@ -1,13 +1,17 @@ package com.tangem.domain.tokens.model.analytics import com.tangem.core.analytics.models.AnalyticsEvent +import com.tangem.core.analytics.models.AnalyticsParam.Key.TOKEN_PARAM sealed class TokenReceiveAnalyticsEvent( event: String, params: Map = mapOf(), ) : AnalyticsEvent("Token / Receive", event, params, null) { - object ReceiveScreenOpened : TokenReceiveAnalyticsEvent(event = "Receive Screen Opened") + class ReceiveScreenOpened(token: String) : TokenReceiveAnalyticsEvent( + event = "Receive Screen Opened", + params = mapOf(TOKEN_PARAM to token), + ) class ButtonCopyAddress(token: String) : TokenReceiveAnalyticsEvent( event = "Button - Copy Address", diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/analytics/MarketDetailsAnalyticsEvent.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/analytics/MarketDetailsAnalyticsEvent.kt index 545104f625..3216b03dd3 100644 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/analytics/MarketDetailsAnalyticsEvent.kt +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/analytics/MarketDetailsAnalyticsEvent.kt @@ -44,6 +44,13 @@ internal class MarketDetailsAnalyticsEvent( "Link" to linkTitle, ), ) + + fun exchangesScreenOpened() = MarketDetailsAnalyticsEvent( + event = "Exchanges Screen Opened", + params = mapOf( + "Token" to token.symbol, + ), + ) } enum class IntervalType(val source: String) { diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/model/MarketsTokenDetailsModel.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/model/MarketsTokenDetailsModel.kt index 28f8360933..11ae6093f5 100644 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/model/MarketsTokenDetailsModel.kt +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/model/MarketsTokenDetailsModel.kt @@ -527,6 +527,8 @@ internal class MarketsTokenDetailsModel @Inject constructor( private fun onListedOnClick(exchangesCount: Int) { modelScope.launch { + analyticsEventHandler.send(analyticsEventBuilder.exchangesScreenOpened()) + showBottomSheet(content = ExchangesBottomSheetContent.Loading(exchangesCount)) // Delay to show the bottom sheet diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/model/MarketsPortfolioModel.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/model/MarketsPortfolioModel.kt index e0e25052fc..c55ba95bec 100644 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/model/MarketsPortfolioModel.kt +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/model/MarketsPortfolioModel.kt @@ -20,7 +20,6 @@ import com.tangem.domain.managetokens.CheckCurrencyUnsupportedUseCase import com.tangem.domain.managetokens.model.CurrencyUnsupportedState import com.tangem.domain.markets.SaveMarketTokensUseCase import com.tangem.domain.markets.TokenMarketInfo -import com.tangem.domain.tokens.model.Network import com.tangem.domain.wallets.models.UserWalletId import com.tangem.domain.wallets.usecase.GetSelectedWalletUseCase import com.tangem.features.markets.impl.R diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt index 540eb5eb32..5f5e518a6c 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt @@ -559,7 +559,7 @@ internal class TokenDetailsViewModel @Inject constructor( viewModelScope.launch(dispatchers.main) { analyticsEventsHandler.send(TokenScreenAnalyticsEvent.ButtonReceive(cryptoCurrency.symbol)) - analyticsEventsHandler.send(TokenReceiveAnalyticsEvent.ReceiveScreenOpened) + analyticsEventsHandler.send(TokenReceiveAnalyticsEvent.ReceiveScreenOpened(cryptoCurrency.symbol)) internalUiState.value = stateFactory.getStateWithReceiveBottomSheet( currency = cryptoCurrency, diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/analytics/WalletScreenAnalyticsEvent.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/analytics/WalletScreenAnalyticsEvent.kt index 9eadef3def..bfb7efaf45 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/analytics/WalletScreenAnalyticsEvent.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/analytics/WalletScreenAnalyticsEvent.kt @@ -87,7 +87,12 @@ sealed class WalletScreenAnalyticsEvent { data object NoticeScanYourCardTapped : MainScreen(event = "Notice - Scan Your Card Tapped") data object WalletUnlockTapped : MainScreen(event = "Notice - Wallet Unlock Tapped") - data object NetworksUnreachable : MainScreen(event = "Notice - Networks Unreachable") + class NetworksUnreachable( + tokens: List, + ) : MainScreen( + event = "Notice - Networks Unreachable", + params = mapOf("Tokens" to tokens.joinToString()), + ) data object MissingAddresses : MainScreen(event = "Notice - Missing Addresses") diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/analytics/utils/TokenListAnalyticsSender.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/analytics/utils/TokenListAnalyticsSender.kt index 28dfac6367..c7aefdd0f1 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/analytics/utils/TokenListAnalyticsSender.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/analytics/utils/TokenListAnalyticsSender.kt @@ -166,12 +166,12 @@ internal class TokenListAnalyticsSender @Inject constructor( } private fun sendUnreachableNetworksEventIfNeeded(currenciesStatuses: List) { - val hasUnreachableCurrencies = currenciesStatuses.any { - it.value is CryptoCurrencyStatus.Unreachable - } + val unreachableCurrencies = currenciesStatuses + .filter { it.value is CryptoCurrencyStatus.Unreachable } + .map { it.currency.symbol } - if (hasUnreachableCurrencies) { - analyticsEventHandler.send(MainScreen.NetworksUnreachable) + if (unreachableCurrencies.isNotEmpty()) { + analyticsEventHandler.send(MainScreen.NetworksUnreachable(unreachableCurrencies)) } } } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletCurrencyActionsClickIntents.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletCurrencyActionsClickIntents.kt index 3b57403e42..21fcf81bf1 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletCurrencyActionsClickIntents.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletCurrencyActionsClickIntents.kt @@ -185,7 +185,9 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor( event = TokenScreenAnalyticsEvent.ButtonReceive(cryptoCurrencyStatus.currency.symbol), ) - analyticsEventHandler.send(event = TokenReceiveAnalyticsEvent.ReceiveScreenOpened) + analyticsEventHandler.send( + event = TokenReceiveAnalyticsEvent.ReceiveScreenOpened(cryptoCurrencyStatus.currency.symbol), + ) stateHolder.showBottomSheet( createReceiveBottomSheetContent( From 7c92486eced6463953ac42aff5ff46430bd173d1 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 31 Oct 2024 13:50:00 +0500 Subject: [PATCH 057/106] Updated on 2026-08-14 --- .../impl/presentation/state/confirm/SendNotificationFactory.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/confirm/SendNotificationFactory.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/confirm/SendNotificationFactory.kt index 48ec5be5b1..bfe558339a 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/confirm/SendNotificationFactory.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/confirm/SendNotificationFactory.kt @@ -172,7 +172,7 @@ internal class SendNotificationFactory( ) }, ) - if (!BlockchainUtils.isCardano(currency.id.value)) { + if (!BlockchainUtils.isCardano(currency.network.id.value)) { addDustWarningNotification( dustValue = currencyCheck.dustValue, feeValue = feeValue, From 0fdc1ac6d1f59f23179c8322caf1739febf2637b Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 31 Oct 2024 14:00:30 +0500 Subject: [PATCH 058/106] Updated on 2026-08-14 --- .../core/ui/extensions/BlockchainIcons.kt | 168 +----------------- 1 file changed, 2 insertions(+), 166 deletions(-) diff --git a/core/ui/src/main/java/com/tangem/core/ui/extensions/BlockchainIcons.kt b/core/ui/src/main/java/com/tangem/core/ui/extensions/BlockchainIcons.kt index ec74fa9c24..8c2054bf91 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/extensions/BlockchainIcons.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/extensions/BlockchainIcons.kt @@ -79,89 +79,7 @@ fun getActiveIconRes(blockchainId: String): Int { "energy-web-chain", "energy-web-chain/test" -> R.drawable.img_energy_web_22 "energy-web-x", "energy-web-x/test" -> R.drawable.img_energy_web_22 "core", "core/test" -> R.drawable.img_core_22 - "casper-network", "casper-network/test" -> R.drawable.img_casper_22 - "xodex" -> R.drawable.img_xodex_22 - else -> R.drawable.ic_alert_24 - } -} - -@Suppress("ComplexMethod", "LongMethod") -@DrawableRes -fun getActiveIconResByNetworkId(networkId: String): Int { - return when (networkId) { - "arbitrum-one", "arbitrum-one/test" -> R.drawable.img_arbitrum_22 - "avalanche", "avalanche-2", "avalanche/test", "avalanche-2/test" -> R.drawable.img_avalanche_22 - "binance-smart-chain", "binance-smart-chain/test", "binancecoin", "binancecoin/test" -> R.drawable.img_bsc_22 - "bitcoin", "bitcoin/test" -> R.drawable.img_btc_22 - "bitcoin-cash", "bitcoin-cash/test" -> R.drawable.img_btc_cash_22 - "litecoin", "litecoin/test" -> R.drawable.img_litecoin_22 - "ethereum", "ethereum/test" -> R.drawable.img_eth_22 - "ethereum-classic", "ethereum-classic/test" -> R.drawable.img_eth_classic_22 - "rootstock" -> R.drawable.img_rsk_22 - "cardano", "cardano/test" -> R.drawable.img_cardano_22 - "tezos" -> R.drawable.img_tezos_22 - "xrp", "ripple" -> R.drawable.img_xrp_22 - "stellar", "stellar/test" -> R.drawable.img_stellar_22 - "polygon-pos", "polygon-pos/test" -> R.drawable.img_polygon_22 - "solana", "solana/test" -> R.drawable.img_solana_22 - "fantom", "fantom/test" -> R.drawable.img_fantom_22 - "dogecoin" -> R.drawable.img_dogecoin_22 - "tron", "tron/test" -> R.drawable.img_tron_22 - "xdai" -> R.drawable.img_gnosis_22 - "ethereum-pow-iou", "ethereum-pow-iou/test" -> R.drawable.img_eth_pow_22 - "ethereumfair", "dischain" -> R.drawable.img_dischain_22 - "polkadot", "polkadot/test" -> R.drawable.img_polkadot_22 - "kusama" -> R.drawable.img_kusama_22 - "optimistic-ethereum", "optimistic-ethereum/test" -> R.drawable.img_optimism_22 - "dash" -> R.drawable.img_dash_22 - "kaspa" -> R.drawable.img_kaspa_22 - "the-open-network", "the-open-network/test" -> R.drawable.img_ton_22 - "kava", "kava/test" -> R.drawable.img_kava_22 - "ravencoin", "ravencoin/test" -> R.drawable.img_ravencoin_22 - "cosmos", "cosmos/test" -> R.drawable.img_cosmos_22 - "terra", "terra-luna" -> R.drawable.img_terra_22 - "terra-2", "terra-luna-2" -> R.drawable.img_terra2_22 - "cronos" -> R.drawable.img_cronos_22 - "telos", "telos/test" -> R.drawable.img_telos_22 - "aleph-zero", "aleph-zero/test" -> R.drawable.img_azero_22 - "octaspace", "octaspace/test" -> R.drawable.img_octaspace_22 - "chia", "chia/test" -> R.drawable.img_chia_22 - "near-protocol", "near-protocol/test" -> R.drawable.img_near_22 - "decimal", "decimal/test" -> R.drawable.img_decimal_22 - "xdc-network", "xdc-network/test" -> R.drawable.img_xdc_22 - "vechain", "vechain/test" -> R.drawable.img_vechain_22 - "aptos", "aptos/test" -> R.drawable.img_aptos_22 - "shibarium", "shibarium/test" -> R.drawable.img_shibarium_22 - "algorand", "algorand/test" -> R.drawable.img_algorand_22 - "hedera-hashgraph", "hedera/test" -> R.drawable.img_hedera_22 - "playa3ull-games" -> R.drawable.img_playa3ull_22 - "ducatus" -> R.drawable.img_ducatus_22 - "aurora", "aurora/test" -> R.drawable.img_aurora_22 - "areon", "areon/test" -> R.drawable.img_areon_22 - "pls", "pls/test" -> R.drawable.img_pls_22 - "zksync", "zksync/test" -> R.drawable.img_zksync_22 - "moonbeam", "moonbeam/test" -> R.drawable.img_moonbeam_22 - "manta-pacific", "manta-pacific/test" -> R.drawable.img_manta_22 - "polygon-zkevm", "polygon-zkevm/test" -> R.drawable.img_polygon_22 - "moonriver", "moonriver/test" -> R.drawable.img_moonriver_22 - "mantle", "mantle/test" -> R.drawable.img_mantle_22 - "flare-network", "flare-network/test" -> R.drawable.img_flare_22 - "taraxa", "taraxa/test" -> R.drawable.img_taraxa_22 - "radiant" -> R.drawable.img_radiant_22 - "base" -> R.drawable.img_base_22 - "joystream" -> R.drawable.img_joystream_22 - "koinos", "koinos/test" -> R.drawable.img_koinos_22 - "bittensor" -> R.drawable.img_bittensor_22 - "blast", "blast/test" -> R.drawable.img_blast_22 - "filecoin" -> R.drawable.img_filecoin_22 - "cyber", "cyber/test" -> R.drawable.img_cyber_22 - "sei", "sei/test" -> R.drawable.img_sei_22 - "internet-computer" -> R.drawable.img_icp_22 - "sui", "sui/test" -> R.drawable.img_sui_22 - "energy-web-chain", "energy-web-chain/test" -> R.drawable.img_energy_web_22 - "energy-web-x", "energy-web-x/test" -> R.drawable.img_energy_web_22 - "core", "core/test" -> R.drawable.img_core_22 - "casper-network", "casper-network/test" -> R.drawable.img_casper_22 + "casper", "casper/test" -> R.drawable.img_casper_22 "xodex" -> R.drawable.img_xodex_22 else -> R.drawable.ic_alert_24 } @@ -322,89 +240,7 @@ fun getGreyedOutIconRes(blockchainId: String): Int { "energy-web-chain", "energy-web-chain/test" -> R.drawable.ic_energy_web_22 "energy-web-x", "energy-web-x/test" -> R.drawable.ic_energy_web_22 "core", "core/test" -> R.drawable.ic_core_22 - "casper-network", "casper-network/test" -> R.drawable.ic_casper_22 - "xodex" -> R.drawable.ic_xodex_22 - else -> R.drawable.ic_alert_24 - } -} - -@Suppress("ComplexMethod", "LongMethod") -@DrawableRes -fun getGreyedOutIconResByNetworkId(networkId: String): Int { - return when (networkId) { - "arbitrum-one", "arbitrum-one/test" -> R.drawable.ic_arbitrum_22 - "avalanche", "avalanche-2", "avalanche/test", "avalanche-2/test" -> R.drawable.ic_avalanche_22 - "binance-smart-chain", "binance-smart-chain/test", "binancecoin", "binancecoin/test" -> R.drawable.ic_bsc_16 - "bitcoin", "bitcoin/test" -> R.drawable.ic_bitcoin_16 - "bitcoin-cash", "bitcoin-cash/test" -> R.drawable.ic_bitcoin_cash_16 - "litecoin", "litecoin/test" -> R.drawable.ic_litecoin_22 - "ethereum", "ethereum/test" -> R.drawable.ic_eth_16 - "ethereum-classic", "ethereum-classic/test" -> R.drawable.ic_eth_16 - "rootstock" -> R.drawable.ic_rsk_16 - "cardano", "cardano/test" -> R.drawable.ic_cardano_16 - "tezos" -> R.drawable.ic_tezos_16 - "xrp", "ripple" -> R.drawable.ic_xrp_22 - "stellar", "stellar/test" -> R.drawable.ic_stellar_16 - "polygon-pos", "polygon-pos/test" -> R.drawable.ic_polygon_22 - "solana", "solana/test" -> R.drawable.ic_solana_16 - "fantom", "fantom/test" -> R.drawable.ic_fantom_22 - "dogecoin" -> R.drawable.ic_dogecoin_16 - "tron", "tron/test" -> R.drawable.ic_tron_22 - "xdai" -> R.drawable.ic_gnosis_22 - "ethereum-pow-iou", "ethereum-pow-iou/test" -> R.drawable.ic_ethereumpow_22 - "ethereumfair", "dischain" -> R.drawable.ic_dischain_22 - "polkadot", "polkadot/test" -> R.drawable.ic_polkadot_16 - "kusama" -> R.drawable.ic_kusama_16 - "optimistic-ethereum", "optimistic-ethereum/test" -> R.drawable.ic_optimism_22 - "dash" -> R.drawable.ic_dash_22 - "kaspa" -> R.drawable.ic_kaspa_22 - "the-open-network", "the-open-network/test" -> R.drawable.ic_ton_22 - "kava", "kava/test" -> R.drawable.ic_kava_22 - "ravencoin", "ravencoin/test" -> R.drawable.ic_ravencoin_22 - "cosmos", "cosmos/test" -> R.drawable.ic_cosmos_22 - "terra", "terra-luna" -> R.drawable.ic_terra_22 - "terra-2", "terra-luna-2" -> R.drawable.ic_terra2_22 - "cronos" -> R.drawable.ic_cronos_22 - "telos", "telos/test" -> R.drawable.ic_telos_22 - "aleph-zero", "aleph-zero/test" -> R.drawable.ic_azero_22 - "octaspace", "octaspace/test" -> R.drawable.ic_octaspace_22 - "chia", "chia/test" -> R.drawable.ic_chia_22 - "near-protocol", "near-protocol/test" -> R.drawable.ic_near_22 - "decimal", "decimal/test" -> R.drawable.ic_decimal_22 - "xdc-network", "xdc-network/test" -> R.drawable.ic_xdc_22 - "vechain", "vechain/test" -> R.drawable.ic_vechain_22 - "aptos", "aptos/test" -> R.drawable.ic_aptos_22 - "shibarium", "shibarium/test" -> R.drawable.ic_shibarium_22 - "algorand", "algorand/test" -> R.drawable.ic_algorand_22 - "hedera-hashgraph", "hedera/test" -> R.drawable.ic_hedera_22 - "playa3ull-games" -> R.drawable.ic_playa3ull_22 - "ducatus" -> R.drawable.ic_ducatus_22 - "aurora", "aurora/test" -> R.drawable.ic_aurora_22 - "areon", "areon/test" -> R.drawable.ic_areon_22 - "pls", "pls/test" -> R.drawable.ic_pls_22 - "zksync", "zksync/test" -> R.drawable.ic_zksync_22 - "moonbeam", "moonbeam/test" -> R.drawable.ic_moonbeam_22 - "manta-pacific", "manta-pacific/test" -> R.drawable.ic_manta_22 - "polygon-zkevm", "polygon-zkevm/test" -> R.drawable.ic_polygon_22 - "moonriver", "moonriver/test" -> R.drawable.ic_moonriver_22 - "mantle", "mantle/test" -> R.drawable.ic_mantle_22 - "flare-network", "flare-network/test" -> R.drawable.ic_flare_22 - "taraxa", "taraxa/test" -> R.drawable.ic_taraxa_22 - "radiant" -> R.drawable.ic_radiant_22 - "base", "base/test" -> R.drawable.ic_base_22 - "joystream" -> R.drawable.ic_joystream_22 - "koinos", "koinos/test" -> R.drawable.ic_koinos_22 - "bittensor" -> R.drawable.ic_bittensor_22 - "blast", "blast/test" -> R.drawable.ic_blast_22 - "filecoin" -> R.drawable.ic_filecoin_22 - "cyber", "cyber/test" -> R.drawable.ic_cyber_22 - "sei", "sei/test" -> R.drawable.ic_sei_22 - "internet-computer" -> R.drawable.ic_icp_22 - "sui", "sui/test" -> R.drawable.ic_sui_22 - "energy-web-chain", "energy-web-chain/test" -> R.drawable.ic_energy_web_22 - "energy-web-x", "energy-web-x/test" -> R.drawable.ic_energy_web_22 - "core", "core/test" -> R.drawable.ic_core_22 - "casper-network", "casper-network/test" -> R.drawable.ic_casper_22 + "casper", "casper/test" -> R.drawable.ic_casper_22 "xodex" -> R.drawable.ic_xodex_22 else -> R.drawable.ic_alert_24 } From fd1e3bb447acdaa9254e4cbbbbf77d0ae05e65ed Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 31 Oct 2024 14:23:33 +0500 Subject: [PATCH 059/106] Updated on 2026-08-14 --- .../core/ui/extensions/BlockchainIcons.kt | 3 ++ .../src/main/res/drawable/ic_canxium_22.xml | 12 +++++++ .../src/main/res/drawable/img_canxium_22.xml | 34 +++++++++++++++++++ .../feature/swap/domain/SwapInteractorImpl.kt | 1 + gradle/dependencies.toml | 2 +- .../tangem/blockchainsdk/utils/Blockchain.kt | 3 ++ 6 files changed, 54 insertions(+), 1 deletion(-) create mode 100644 core/ui/src/main/res/drawable/ic_canxium_22.xml create mode 100644 core/ui/src/main/res/drawable/img_canxium_22.xml diff --git a/core/ui/src/main/java/com/tangem/core/ui/extensions/BlockchainIcons.kt b/core/ui/src/main/java/com/tangem/core/ui/extensions/BlockchainIcons.kt index 8c2054bf91..7a95f9e98c 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/extensions/BlockchainIcons.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/extensions/BlockchainIcons.kt @@ -81,6 +81,7 @@ fun getActiveIconRes(blockchainId: String): Int { "core", "core/test" -> R.drawable.img_core_22 "casper", "casper/test" -> R.drawable.img_casper_22 "xodex" -> R.drawable.img_xodex_22 + "canxium" -> R.drawable.img_canxium_22 else -> R.drawable.ic_alert_24 } } @@ -160,6 +161,7 @@ fun getActiveIconResByCoinId(coinId: String): Int { "core", "core/test" -> R.drawable.img_core_22 "casper-network" -> R.drawable.img_casper_22 "xodex" -> R.drawable.img_xodex_22 + "canxium" -> R.drawable.img_canxium_22 else -> R.drawable.ic_alert_24 } } @@ -242,6 +244,7 @@ fun getGreyedOutIconRes(blockchainId: String): Int { "core", "core/test" -> R.drawable.ic_core_22 "casper", "casper/test" -> R.drawable.ic_casper_22 "xodex" -> R.drawable.ic_xodex_22 + "canxium" -> R.drawable.ic_canxium_22 else -> R.drawable.ic_alert_24 } } \ No newline at end of file diff --git a/core/ui/src/main/res/drawable/ic_canxium_22.xml b/core/ui/src/main/res/drawable/ic_canxium_22.xml new file mode 100644 index 0000000000..6ec1ea1096 --- /dev/null +++ b/core/ui/src/main/res/drawable/ic_canxium_22.xml @@ -0,0 +1,12 @@ + + + + diff --git a/core/ui/src/main/res/drawable/img_canxium_22.xml b/core/ui/src/main/res/drawable/img_canxium_22.xml new file mode 100644 index 0000000000..7e2e3b0263 --- /dev/null +++ b/core/ui/src/main/res/drawable/img_canxium_22.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt index 59e27766ec..77aa2d704c 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt @@ -1046,6 +1046,7 @@ internal class SwapInteractorImpl @AssistedInject constructor( Blockchain.Core, Blockchain.CoreTestnet, Blockchain.Xodex, + Blockchain.Canxium, -> Fee.Common(feeAmount) // endregion } diff --git a/gradle/dependencies.toml b/gradle/dependencies.toml index 427f9a46be..85a683a170 100644 --- a/gradle/dependencies.toml +++ b/gradle/dependencies.toml @@ -88,7 +88,7 @@ markdownComposeView = "0.5.4" # endregion Other libraries # region Tangem -tangemBlockchainSdk = "develop-844" +tangemBlockchainSdk = "develop-845" #tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds tangemCardSdk = "develop-395" #tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^ diff --git a/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/utils/Blockchain.kt b/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/utils/Blockchain.kt index 2ae1e364a2..8a4ac6fc89 100644 --- a/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/utils/Blockchain.kt +++ b/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/utils/Blockchain.kt @@ -139,6 +139,7 @@ fun Blockchain.Companion.fromNetworkId(networkId: String): Blockchain? { "casper-network" -> Blockchain.Casper "casper-network/test" -> Blockchain.CasperTestnet "xodex" -> Blockchain.Xodex + "canxium" -> Blockchain.Canxium else -> null } } @@ -277,6 +278,7 @@ fun Blockchain.toNetworkId(): String { Blockchain.Core -> "core" Blockchain.CoreTestnet -> "core/test" Blockchain.Xodex -> "xodex" + Blockchain.Canxium -> "canxium" } } @@ -367,6 +369,7 @@ fun Blockchain.toCoinId(): String { Blockchain.Casper, Blockchain.CasperTestnet -> "casper-network" Blockchain.Core, Blockchain.CoreTestnet -> "coredaoorg" Blockchain.Xodex -> "xodex" + Blockchain.Canxium -> "canxium" } } From d381771fe71f088c54e8828bfb437f5c0b1911c8 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 31 Oct 2024 16:18:39 +0500 Subject: [PATCH 060/106] Updated on 2026-08-14 --- .../state/transformers/SetButtonsStateTransformer.kt | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetButtonsStateTransformer.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetButtonsStateTransformer.kt index c2fcce7f80..5d7d0260e8 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetButtonsStateTransformer.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetButtonsStateTransformer.kt @@ -155,14 +155,15 @@ internal class SetButtonsStateTransformer( } private fun StakingUiState.onConfirmationClick() { - if (confirmationState is StakingStates.ConfirmationState.Data) { + val confirmationState = confirmationState as? StakingStates.ConfirmationState.Data + val amountState = amountState as? AmountState.Data + if (confirmationState != null && amountState != null) { if (confirmationState.innerState == InnerConfirmationStakingState.COMPLETED) { clickIntents.onNextClick() } else { + val amount = amountState.amountTextField.cryptoAmount.value.orZero() val isEnterAction = actionType == StakingActionCommonType.Enter - val isApproveNeeded = confirmationState.isApprovalNeeded - - if (isEnterAction && isApproveNeeded) { + if (isEnterAction && confirmationState.isApprovalNeeded && confirmationState.allowance < amount) { clickIntents.showApprovalBottomSheet() } else { clickIntents.onActionClick() From 441e8b2b7a985454392f9b385ffed6c0ff50a0c5 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 28 Oct 2024 19:44:32 +0200 Subject: [PATCH 061/106] Updated on 2026-08-14 --- .../ui/components/TokenDetailsBalanceBlock.kt | 82 ++++++++++++------- 1 file changed, 51 insertions(+), 31 deletions(-) diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/TokenDetailsBalanceBlock.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/TokenDetailsBalanceBlock.kt index 74ab07f465..54e6b0dc2b 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/TokenDetailsBalanceBlock.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/TokenDetailsBalanceBlock.kt @@ -11,6 +11,7 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider +import androidx.constraintlayout.compose.ConstraintLayout import com.tangem.core.ui.components.RectangleShimmer import com.tangem.core.ui.components.buttons.HorizontalActionChips import com.tangem.core.ui.components.buttons.segmentedbutton.SegmentedButtons @@ -25,6 +26,7 @@ import com.tangem.feature.tokendetails.presentation.tokendetails.state.component import com.tangem.features.tokendetails.impl.R import kotlinx.collections.immutable.toImmutableList +@Suppress("DestructuringDeclarationWithTooManyEntries") @Composable internal fun TokenDetailsBalanceBlock( state: TokenDetailsBalanceBlockState, @@ -36,43 +38,61 @@ internal fun TokenDetailsBalanceBlock( shape = TangemTheme.shapes.roundedCornersXMedium, color = TangemTheme.colors.background.primary, ) { - Column { - Row( - verticalAlignment = Alignment.CenterVertically, - modifier = Modifier - .padding(horizontal = TangemTheme.dimens.spacing12) - .fillMaxWidth() - .heightIn(min = TangemTheme.dimens.spacing24), - ) { - Text( - text = stringResource(id = R.string.common_balance_title), - color = TangemTheme.colors.text.tertiary, - style = TangemTheme.typography.subtitle2, - maxLines = 1, - modifier = Modifier - .weight(1f) - .padding(top = TangemTheme.dimens.spacing12), - ) - BalanceButtons(state) - } + val spacing4 = TangemTheme.dimens.spacing4 + val spacing10 = TangemTheme.dimens.spacing10 + val spacing12 = TangemTheme.dimens.spacing12 + + ConstraintLayout( + modifier = Modifier + .fillMaxWidth(), + ) { + val (balanceTitle, toggleButtons, fiatBalance, cryptoBalance, actionChips) = createRefs() + + Text( + text = stringResource(id = R.string.common_balance_title), + color = TangemTheme.colors.text.tertiary, + style = TangemTheme.typography.subtitle2, + modifier = Modifier.constrainAs(balanceTitle) { + top.linkTo(anchor = parent.top, margin = spacing12) + start.linkTo(anchor = parent.start, margin = spacing12) + }, + ) + + BalanceButtons( + state = state, + modifier = Modifier.constrainAs(toggleButtons) { + top.linkTo(anchor = parent.top) + end.linkTo(anchor = parent.end, margin = spacing10) + }, + ) + FiatBalance( state = state, isBalanceHidden = isBalanceHidden, - modifier = Modifier - .padding(top = TangemTheme.dimens.spacing4) - .padding(horizontal = TangemTheme.dimens.spacing12), + modifier = Modifier.constrainAs(fiatBalance) { + top.linkTo(anchor = balanceTitle.bottom, margin = spacing4) + start.linkTo(anchor = parent.start, margin = spacing12) + }, ) + CryptoBalance( state = state, isBalanceHidden = isBalanceHidden, - modifier = Modifier - .padding(top = TangemTheme.dimens.spacing4) - .padding(horizontal = TangemTheme.dimens.spacing12), + modifier = Modifier.constrainAs(cryptoBalance) { + top.linkTo(anchor = fiatBalance.bottom, margin = spacing4) + start.linkTo(anchor = parent.start, margin = spacing12) + }, ) HorizontalActionChips( buttons = state.actionButtons.map(TokenDetailsActionButton::config).toImmutableList(), - modifier = Modifier.padding(vertical = TangemTheme.dimens.spacing12), + modifier = Modifier + .constrainAs(actionChips) { + top.linkTo(anchor = cryptoBalance.bottom, margin = spacing12) + start.linkTo(anchor = parent.start) + end.linkTo(anchor = parent.end) + bottom.linkTo(anchor = parent.bottom, margin = spacing12) + }, contentPadding = PaddingValues(horizontal = TangemTheme.dimens.spacing12), ) } @@ -89,7 +109,7 @@ private fun FiatBalance( is TokenDetailsBalanceBlockState.Loading -> RectangleShimmer( modifier = modifier.size( width = TangemTheme.dimens.size102, - height = TangemTheme.dimens.size24, + height = TangemTheme.dimens.size32, ), ) is TokenDetailsBalanceBlockState.Content -> Text( @@ -136,14 +156,14 @@ private fun CryptoBalance( } @Composable -private fun BalanceButtons(state: TokenDetailsBalanceBlockState) { +private fun BalanceButtons(state: TokenDetailsBalanceBlockState, modifier: Modifier = Modifier) { if (state !is TokenDetailsBalanceBlockState.Content || !state.isBalanceSelectorEnabled) return SegmentedButtons( config = state.balanceSegmentedButtonConfig, onClick = state.onBalanceSelect, showIndication = false, - modifier = Modifier + modifier = modifier .padding(top = TangemTheme.dimens.spacing11) .width(IntrinsicSize.Min), ) { config -> @@ -159,8 +179,8 @@ private fun BalanceButtons(state: TokenDetailsBalanceBlockState) { maxLines = 1, modifier = Modifier .padding( - horizontal = TangemTheme.dimens.spacing4, - vertical = TangemTheme.dimens.spacing6, + horizontal = TangemTheme.dimens.spacing6, + vertical = TangemTheme.dimens.spacing4, ) .align(Alignment.Center), ) From 58e611012175a75c6570537918b217cc81f0128e Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 31 Oct 2024 12:27:23 +0500 Subject: [PATCH 062/106] Updated on 2026-08-14 --- .../models/response/ExchangeProvider.kt | 3 ++ core/res/src/main/res/values-de/strings.xml | 3 ++ core/res/src/main/res/values-es/strings.xml | 1 + core/res/src/main/res/values-fr/strings.xml | 1 + core/res/src/main/res/values-ja/strings.xml | 3 ++ core/res/src/main/res/values-ru/strings.xml | 3 +- core/res/src/main/res/values/strings.xml | 3 +- .../java/com/tangem/utils/StringsSigns.kt | 1 + .../swap/converters/SwapPairInfoConverter.kt | 2 ++ .../domain/models/domain/SwapPairLeast.kt | 1 + .../tangem/feature/swap/models/SwapAlertUM.kt | 4 +-- .../tangem/feature/swap/ui/StateBuilder.kt | 30 +++++++++++++------ .../feature/swap/viewmodels/SwapViewModel.kt | 2 +- 13 files changed, 43 insertions(+), 14 deletions(-) diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/express/models/response/ExchangeProvider.kt b/core/datasource/src/main/java/com/tangem/datasource/api/express/models/response/ExchangeProvider.kt index bda2f0e17f..b4ffbd4506 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/express/models/response/ExchangeProvider.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/express/models/response/ExchangeProvider.kt @@ -26,6 +26,9 @@ data class ExchangeProvider( @Json(name = "recommended") val isRecommended: Boolean = false, + + @Json(name = "slippage") + val slippage: Int?, ) enum class ExchangeProviderType { diff --git a/core/res/src/main/res/values-de/strings.xml b/core/res/src/main/res/values-de/strings.xml index 745138a008..7fdccc468b 100644 --- a/core/res/src/main/res/values-de/strings.xml +++ b/core/res/src/main/res/values-de/strings.xml @@ -551,9 +551,11 @@ Zugangscode wiederherstellen Identische Karten oder Ring Zugangscode + Bezahlen mit Unsere Dienstleistungen sind in diesem Land nicht verfügbar Änder oder bestätige bitte Dein Wohnsitz wurde identifiziert als + Über Gruppe erstellen Nach Guthaben Token organisieren @@ -742,6 +744,7 @@ Beim Staking im %1$s -Netzwerk mit einem neuen Validator werden alle zuvor eingesetzten Kryptos automatisch an diesen Validator übertragen Reinvestiert Deine verdienten Prämien in Deinen Einsatzbetrag und erhöht so den potenziellen Gewinn. Mit Restake kannst Du Dein Guthaben von einem Validator zu einem anderen verschieben, ohne dass Du den Stake aufheben musst. + Du bist dabei, Dein gesamtes Guthaben zu staken. Wir empfehlen, einen kleinen Betrag übrig zu lassen, um die Netzwerkgebühren für die Aufhebung des Stakes oder das Einfordern von Prämien abzudecken. Entsperre dein Geld, um es aus dem Staking-Prozess abzuheben. Das Freischalten nimmt %s. Nach Ablauf der 21-tägigen Bindungsfrist kannst Du über Dein Guthaben verfügen. Die Prämie wird zusammen mit dem ungestaketen Guthaben abgehoben. Deine Assets steht Dir nach Ablauf der Frist für die Aufhebung der Bindung %s zur Verfügung. diff --git a/core/res/src/main/res/values-es/strings.xml b/core/res/src/main/res/values-es/strings.xml index 6703130663..03a98284df 100644 --- a/core/res/src/main/res/values-es/strings.xml +++ b/core/res/src/main/res/values-es/strings.xml @@ -742,6 +742,7 @@ El staking en la red %1$s con un nuevo validador transferirá automáticamente todos los fondos previamente en staking a este validador. Reinvierta las recompensas obtenidas en el monto apostado, aumentando las ganancias potenciales. La opción de volver a staking le permite mover sus fondos de un validador a otro sin necesidad de retirarlos. + Está a punto de hacer staking con todo su saldo. Recomendamos dejar una cantidad para cubrir las tarifas de la red por unstaking o reclamo de recompensas. Desbloquee su dinero para retirarlo del proceso de staking. El desbloqueo demora %s minuto. Sus fondos estarán disponibles para su uso después del período de desvinculación de 21 días. La recompensa se retirará junto con los fondos de desvinculación. Sus fondos estarán disponibles para su uso después del período de desvinculación de %s. diff --git a/core/res/src/main/res/values-fr/strings.xml b/core/res/src/main/res/values-fr/strings.xml index 80f7d2e337..7882e38152 100644 --- a/core/res/src/main/res/values-fr/strings.xml +++ b/core/res/src/main/res/values-fr/strings.xml @@ -742,6 +742,7 @@ Le staking dans le réseau %1$s avec un nouveau validateur transférera automatiquement tous les fonds précédemment stakés vers ce validateur Réinvestissez vos récompenses gagnées dans le montant que vous avez staké, augmentant ainsi vos gains potentiels. L\'option de restaker vous permet de déplacer vos fonds d\'un validateur à un autre sans avoir besoin de les déstaker. + Vous êtes sur le point de staker l\'intégralité de votre solde. Nous vous recommandons de laisser un petit montant pour couvrir les frais de réseau pour unstaking ou la réclamation des récompenses. Débloquez votre argent pour le retirer du processus de staking. Le déverrouillage prend %s. Vos fonds seront disponibles à l\'utilisation après la période de déblocage de 21 jours. La récompense sera retirée en même temps que vos fonds de déblocage. Vos fonds seront disponibles pour utilisation après la période de désengagement %s. diff --git a/core/res/src/main/res/values-ja/strings.xml b/core/res/src/main/res/values-ja/strings.xml index d5ae3921d5..de3d33b7d2 100644 --- a/core/res/src/main/res/values-ja/strings.xml +++ b/core/res/src/main/res/values-ja/strings.xml @@ -543,9 +543,11 @@ アクセスコードの復元 同一のカード アクセスコード + 支払う この国では当社のサービスはご利用いただけません 変更または確認 あなたの住居は次のように識別されています + 経由 グループ 残高順 トークンを整理する @@ -732,6 +734,7 @@ 新しいバリデーターで%1$sネットワークにステーキングすると、以前にステーキングされた資金はすべてこのバリデーターに自動的に転送されます。 獲得した報酬をステーキングに再投資し、潜在的な収益を増やします。 再ステーキングを使うと、ステーキングを解除することなく、あるバリデータから別のバリデータに資金を移動できます。 + 残高のすべてをステーキングしようとしています。ステーキング解除や報酬請求にかかるネットワーク手数料をカバーするために、少額を残しておくことをお勧めします。 資金をステーキングから引き出すには、ロックを解除してください。ロック解除には%sかかります。 資金は、21日間のロック解除期間後に使用可能になります。報酬は、ロック解除後の資金とともに引き出されます。 %sのステーキング解除期間後、資金はすぐ利用可能となります。 diff --git a/core/res/src/main/res/values-ru/strings.xml b/core/res/src/main/res/values-ru/strings.xml index 884e4e6cc6..ba6d9c4db6 100644 --- a/core/res/src/main/res/values-ru/strings.xml +++ b/core/res/src/main/res/values-ru/strings.xml @@ -825,7 +825,8 @@ Новый провайдер обмена! В сумму включено: \n• комиссия провайдера сервиса\n• комиссия сети за отправку %s от биржи обратно на адрес пользователя. В сумму включена комиссия провайдера сервиса. - Комиссии + Проскальзывание провайдера составляет до %s. + Информация Подтверждения считаются отраслевым стандартом для всех децентрализованных бирж и защищают ваш кошелек от доступа со стороны смарт-контракта без вашего разрешения. По замыслу смарт-контракты не могут получить доступ к вашим токенам, если вы не одобрите доступ со своей стороны. «Разблокируя» свои токены, вы даете смарт-контракту 1inch разрешение тратить ваши активы. Майнеры сети получают компенсацию за газ (оплачиваемый вами) за запись этого действия в блокчейне. Как только разрешение будет предоставлено, вы сможете обменять свой токен. Подтвердить Ошибка расчета комиссии. Пожалуйста, отправьте информацию в поддержку. diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml index 486f321553..0e98aa2383 100644 --- a/core/res/src/main/res/values/strings.xml +++ b/core/res/src/main/res/values/strings.xml @@ -810,7 +810,8 @@ New Swap Provider Available! The amount includes:\n• service provider\'s fee\n• network fee for sending %s from the exchange back to the user\'s address. The amount includes the service provider\'s fee. - Fees + Provider slippage is up to %s. + Information All decentralized exchanges require approvals to prevent smart contracts from accessing your wallet without your permission. By design, smart contracts can\'t access your tokens unless you approve. By \"unlocking\" your tokens, you authorize the 1-inch smart contract to spend them. The network\'s miners receive a gas fee (paid by you) to record this action on the blockchain. You can swap your token after giving approval. Approve Fee estimation error. Please send feedback to support. diff --git a/core/utils/src/main/java/com/tangem/utils/StringsSigns.kt b/core/utils/src/main/java/com/tangem/utils/StringsSigns.kt index 991f77b883..b47160124d 100644 --- a/core/utils/src/main/java/com/tangem/utils/StringsSigns.kt +++ b/core/utils/src/main/java/com/tangem/utils/StringsSigns.kt @@ -10,4 +10,5 @@ object StringsSigns { const val TILDE_SIGN = "~" const val INFINITY_SIGN = "∞" const val NON_BREAKING_SPACE = '\u00A0' + const val PERCENT = "%" } \ No newline at end of file diff --git a/features/swap/data/src/main/java/com/tangem/feature/swap/converters/SwapPairInfoConverter.kt b/features/swap/data/src/main/java/com/tangem/feature/swap/converters/SwapPairInfoConverter.kt index ec5a88e56e..17850cb1ba 100644 --- a/features/swap/data/src/main/java/com/tangem/feature/swap/converters/SwapPairInfoConverter.kt +++ b/features/swap/data/src/main/java/com/tangem/feature/swap/converters/SwapPairInfoConverter.kt @@ -46,6 +46,7 @@ class SwapPairInfoConverter : Converter Unit), ) : SwapAlertUM() { override val title: TextReference = resourceReference( - com.tangem.feature.swap.presentation.R.string.swapping_alert_title, + R.string.swapping_alert_title, ) override val confirmButtonText: TextReference = resourceReference(id = R.string.common_ok) diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt index d6f9cf8bc5..8176ed64cf 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt @@ -34,6 +34,7 @@ import com.tangem.feature.swap.utils.getExpressErrorTitle import com.tangem.feature.swap.viewmodels.SwapProcessDataState import com.tangem.utils.Provider import com.tangem.utils.StringsSigns.DASH_SIGN +import com.tangem.utils.StringsSigns.PERCENT import com.tangem.utils.StringsSigns.TILDE_SIGN import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.toImmutableList @@ -1089,28 +1090,39 @@ internal class StateBuilder( uiState: SwapStateHolder, isPriceImpact: Boolean, token: String, - providerType: ExchangeProviderType, + provider: SwapProvider, onDismiss: () -> Unit, ): SwapStateHolder { - val message = when (providerType) { - ExchangeProviderType.CEX -> resourceReference(R.string.swapping_alert_cex_description, wrappedList(token)) - ExchangeProviderType.DEX, ExchangeProviderType.DEX_BRIDGE -> { - val refs = buildList { + val combinedMessage = buildList { + when (provider.type) { + ExchangeProviderType.CEX -> { + add(resourceReference(R.string.swapping_alert_cex_description, wrappedList(token))) + } + ExchangeProviderType.DEX, + ExchangeProviderType.DEX_BRIDGE, + -> { if (isPriceImpact) { add(resourceReference(R.string.swapping_high_price_impact_description)) add(stringReference("\n\n")) } add(resourceReference(R.string.swapping_alert_dex_description)) } - - combinedReference(refs.toWrappedList()) + } + provider.slippage?.let { slippage -> + add(stringReference("\n\n")) + add( + resourceReference( + R.string.swapping_alert_slippage_description, + wrappedList("$slippage$PERCENT"), + ), + ) } } return uiState.copy( event = triggeredEvent( SwapEvent.ShowAlert( - SwapAlertUM.FeesAlert( - message = message, + SwapAlertUM.InformationAlert( + message = combinedReference(combinedMessage.toWrappedList()), onConfirmClick = onDismiss, ), ), diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/viewmodels/SwapViewModel.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/viewmodels/SwapViewModel.kt index 0003e45237..c4854ddb29 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/viewmodels/SwapViewModel.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/viewmodels/SwapViewModel.kt @@ -1067,7 +1067,7 @@ internal class SwapViewModel @Inject constructor( uiState = uiState, isPriceImpact = isPriceImpact, token = currencySymbol, - providerType = selectedProvider.type, + provider = selectedProvider, ) { uiState = stateBuilder.clearAlert(uiState) } From 8b91930e18e56f59c3bac6fa27bb0dc8322d2cbe Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 31 Oct 2024 15:21:06 +0300 Subject: [PATCH 063/106] Updated on 2026-08-14 --- .../domain/walletconnect2/app/TangemWcBlockchainHelper.kt | 8 ++++++++ .../walletconnect2/app/WalletConnectEventsHandlerImpl.kt | 2 +- .../domain/walletconnect2/domain/WcBlockchainHelper.kt | 3 +++ .../details/redux/walletconnect/WalletConnectAction.kt | 3 ++- .../redux/walletconnect/WalletConnectMiddleware.kt | 4 ++++ 5 files changed, 18 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect2/app/TangemWcBlockchainHelper.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect2/app/TangemWcBlockchainHelper.kt index 8ed86f9fa2..07700916ac 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletconnect2/app/TangemWcBlockchainHelper.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletconnect2/app/TangemWcBlockchainHelper.kt @@ -24,6 +24,12 @@ internal class TangemWcBlockchainHelper( return blockchain?.toNetworkId() } + override fun chainIdsToBlockchains(chainIds: List): List { + return chainIds.mapNotNull { + it.parseId()?.chainIdToBlockchain() + }.distinct() + } + override fun chainIdToMissingNetworkNameOrNull(chainId: String): String? { val parsedId = chainId.parseId() ?: return null val blockchain = parsedId.chainIdToBlockchain() @@ -103,6 +109,7 @@ internal class TangemWcBlockchainHelper( second.toIntOrNull() ?.let(Blockchain::fromChainId) } + SOLANA_NAMESPACE -> Blockchain.Solana else -> { Blockchain.fromNetworkId(networkId = first) .takeIf(supportedNonEvmBlockchains::contains) @@ -112,6 +119,7 @@ internal class TangemWcBlockchainHelper( private companion object { const val EVM_NAMESPACE = "eip155" + const val SOLANA_NAMESPACE = "solana" const val CHAIN_SEPARATOR = ":" const val TESTNET_SEPARATOR = "/" } diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect2/app/WalletConnectEventsHandlerImpl.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect2/app/WalletConnectEventsHandlerImpl.kt index 073fcb8f0d..d84263034d 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletconnect2/app/WalletConnectEventsHandlerImpl.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletconnect2/app/WalletConnectEventsHandlerImpl.kt @@ -19,7 +19,7 @@ internal class WalletConnectEventsHandlerImpl : WalletConnectEventsHandler { WalletConnectDialog.SessionProposalDialog( sessionProposal = proposal, networks = networksFormatted, - onApprove = { store.dispatchOnMain(WalletConnectAction.ApproveProposal) }, + onApprove = { store.dispatchOnMain(WalletConnectAction.ApproveProposal(proposal)) }, onReject = { store.dispatchOnMain(WalletConnectAction.RejectProposal) }, ), ), diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/WcBlockchainHelper.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/WcBlockchainHelper.kt index 111c5c3d6f..91771ac6e1 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/WcBlockchainHelper.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/WcBlockchainHelper.kt @@ -1,5 +1,6 @@ package com.tangem.tap.domain.walletconnect2.domain +import com.tangem.blockchain.common.Blockchain import com.tangem.tap.domain.walletconnect2.domain.models.Account interface WcBlockchainHelper { @@ -14,4 +15,6 @@ interface WcBlockchainHelper { fun chainIdToFullNameOrNull(chainId: String): String? fun chainIdsToAccounts(walletAddress: String, chainIds: List, derivationPath: String?): List + + fun chainIdsToBlockchains(chainIds: List): List } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectAction.kt b/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectAction.kt index a6c5dabb19..06591c01b0 100644 --- a/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectAction.kt +++ b/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectAction.kt @@ -2,6 +2,7 @@ package com.tangem.tap.features.details.redux.walletconnect import com.tangem.tap.domain.walletconnect2.domain.WcPreparedRequest import com.tangem.tap.domain.walletconnect2.domain.models.WalletConnectError +import com.tangem.tap.domain.walletconnect2.domain.models.WalletConnectEvents import com.tangem.tap.features.details.ui.walletconnect.WcSessionForScreen import org.rekotlin.Action @@ -22,7 +23,7 @@ sealed class WalletConnectAction : Action { data class ShowClipboardOrScanQrDialog(val wcUri: String) : WalletConnectAction() //region WalletConnect 2.0 - data object ApproveProposal : WalletConnectAction() + data class ApproveProposal(val proposal: WalletConnectEvents.SessionProposal) : WalletConnectAction() data object RejectProposal : WalletConnectAction() data object SessionEstablished : WalletConnectAction() diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectMiddleware.kt b/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectMiddleware.kt index 27df30674a..facfd60eab 100644 --- a/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectMiddleware.kt @@ -85,7 +85,11 @@ class WalletConnectMiddleware { } is WalletConnectAction.ApproveProposal -> { scope.launch { + val proposalChainIds = action.proposal.requiredChainIds + action.proposal.optionalChainIds + val proposalBlockchains = + walletConnectInteractor.blockchainHelper.chainIdsToBlockchains(proposalChainIds) val accounts = getWalletManagers() + .filter { walletManager -> proposalBlockchains.contains(walletManager.wallet.blockchain) } .flatMap { val wallet = it.wallet val chainIds = walletConnectInteractor.blockchainHelper.networkIdToChainIdOrNull( From 45273dbc9faec4679dc9d653e6ba41d5abb52836 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 31 Oct 2024 16:21:46 +0300 Subject: [PATCH 064/106] Updated on 2026-08-14 --- .../src/main/assets/configs/feature_toggles_config.json | 4 ---- 1 file changed, 4 deletions(-) diff --git a/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json b/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json index 5191b7778d..2751047561 100644 --- a/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json +++ b/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json @@ -11,10 +11,6 @@ "name": "STAKING_ENABLED", "version": "5.15.0" }, - { - "name": "MARKETS_ENABLED", - "version": "5.15.0" - }, { "name": "IS_ETHEREUM_EIP_1559_ENABLED", "version": "5.17.0" From f3ee1dd9a965f2ba1af5b5147986957a0ab00b26 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 30 Oct 2024 18:24:10 +0400 Subject: [PATCH 065/106] Updated on 2026-08-14 --- .../components/currency/icon/ContentIcon.kt | 1 + .../components/currency/icon/CurrencyIcon.kt | 23 ++++ .../currency/icon/CurrencyIconState.kt | 10 ++ .../ui}/src/main/res/drawable/ic_empty_64.xml | 0 .../onramp/buy/DefaultBuyCryptoComponent.kt | 6 +- .../DefaultOnrampOperationComponent.kt | 2 +- .../selecttoken/OnrampOperationComponent.kt | 7 +- .../onramp/sell/DefaultSellCryptoComponent.kt | 6 +- .../onramp/swap/entity/ExchangeCardUM.kt | 63 +++++++++ .../features/onramp/swap/ui/ExchangeCard.kt | 124 ++++++++++++++++++ 10 files changed, 225 insertions(+), 17 deletions(-) rename {features/wallet/impl => core/ui}/src/main/res/drawable/ic_empty_64.xml (100%) create mode 100644 features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/entity/ExchangeCardUM.kt create mode 100644 features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/ui/ExchangeCard.kt diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/currency/icon/ContentIcon.kt b/core/ui/src/main/java/com/tangem/core/ui/components/currency/icon/ContentIcon.kt index 089d050c7d..8d1787952d 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/currency/icon/ContentIcon.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/currency/icon/ContentIcon.kt @@ -53,6 +53,7 @@ internal fun ContentIcon( ) CurrencyIconState.Loading, CurrencyIconState.Locked, + is CurrencyIconState.Empty, -> Unit } } diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/currency/icon/CurrencyIcon.kt b/core/ui/src/main/java/com/tangem/core/ui/components/currency/icon/CurrencyIcon.kt index d06e59fc30..1b92960c51 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/currency/icon/CurrencyIcon.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/currency/icon/CurrencyIcon.kt @@ -1,15 +1,19 @@ package com.tangem.core.ui.components.currency.icon +import androidx.annotation.DrawableRes import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.BoxScope import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.size import androidx.compose.foundation.shape.CircleShape +import androidx.compose.material3.Icon import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.unit.dp import com.tangem.core.ui.components.CircleShimmer import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.utils.getGreyScaleColorFilter @@ -33,6 +37,7 @@ fun CurrencyIcon(state: CurrencyIconState, modifier: Modifier = Modifier, should when (state) { is CurrencyIconState.Loading -> LoadingIcon(modifier = iconModifier) is CurrencyIconState.Locked -> LockedIcon(modifier = iconModifier) + is CurrencyIconState.Empty -> EmptyIcon(resId = state.resId, modifier = iconModifier) is CurrencyIconState.CoinIcon, is CurrencyIconState.CustomTokenIcon, is CurrencyIconState.TokenIcon, @@ -66,6 +71,24 @@ private fun LockedIcon(modifier: Modifier = Modifier) { } } +@Composable +private fun EmptyIcon(@DrawableRes resId: Int, modifier: Modifier = Modifier) { + Box( + modifier = modifier.background( + color = TangemTheme.colors.button.secondary, + shape = CircleShape, + ), + contentAlignment = Alignment.Center, + ) { + Icon( + painter = painterResource(resId), + contentDescription = null, + modifier = Modifier.size(size = 24.dp), + tint = TangemTheme.colors.icon.informative, + ) + } +} + @Composable private fun BoxScope.ContentIconContainer( icon: CurrencyIconState, diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/currency/icon/CurrencyIconState.kt b/core/ui/src/main/java/com/tangem/core/ui/components/currency/icon/CurrencyIconState.kt index f19975ab9e..371ad59f61 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/currency/icon/CurrencyIconState.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/currency/icon/CurrencyIconState.kt @@ -3,6 +3,7 @@ package com.tangem.core.ui.components.currency.icon import androidx.annotation.DrawableRes import androidx.compose.runtime.Immutable import androidx.compose.ui.graphics.Color +import com.tangem.core.ui.R /** * Represents the various states an icon can be in. @@ -82,6 +83,14 @@ sealed class CurrencyIconState { override val topBadgeIconResId: Int? = null } + data class Empty( + @DrawableRes val resId: Int = R.drawable.ic_empty_64, + ) : CurrencyIconState() { + override val isGrayscale: Boolean = true + override val showCustomBadge: Boolean = false + override val topBadgeIconResId: Int? = null + } + fun copySealed( isGrayscale: Boolean = this.isGrayscale, showCustomBadge: Boolean = this.showCustomBadge, @@ -103,6 +112,7 @@ sealed class CurrencyIconState { ) is Loading, is Locked, + is Empty, -> this } } \ No newline at end of file diff --git a/features/wallet/impl/src/main/res/drawable/ic_empty_64.xml b/core/ui/src/main/res/drawable/ic_empty_64.xml similarity index 100% rename from features/wallet/impl/src/main/res/drawable/ic_empty_64.xml rename to core/ui/src/main/res/drawable/ic_empty_64.xml diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/buy/DefaultBuyCryptoComponent.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/buy/DefaultBuyCryptoComponent.kt index f8b1be4d51..8e36bf09b0 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/buy/DefaultBuyCryptoComponent.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/buy/DefaultBuyCryptoComponent.kt @@ -20,11 +20,7 @@ internal class DefaultBuyCryptoComponent @AssistedInject constructor( private val selectTokenComponent: OnrampOperationComponent = onrampOperationComponentFactory.create( context = appComponentContext, - params = OnrampOperationComponent.Params( - operation = OnrampOperation.BUY, - hasSearchBar = true, - userWalletId = params.userWalletId, - ), + params = OnrampOperationComponent.Params(operation = OnrampOperation.BUY, userWalletId = params.userWalletId), ) @Composable diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selecttoken/DefaultOnrampOperationComponent.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selecttoken/DefaultOnrampOperationComponent.kt index 429df40cb5..454ba6bea6 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selecttoken/DefaultOnrampOperationComponent.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selecttoken/DefaultOnrampOperationComponent.kt @@ -33,7 +33,7 @@ internal class DefaultOnrampOperationComponent @AssistedInject constructor( context = child(key = "token_list"), params = OnrampTokenListComponent.Params( filterOperation = params.operation, - hasSearchBar = params.hasSearchBar, + hasSearchBar = true, userWalletId = params.userWalletId, onTokenClick = ::onTokenClick, ), diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selecttoken/OnrampOperationComponent.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selecttoken/OnrampOperationComponent.kt index 3f6b4fc489..c35a452d58 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selecttoken/OnrampOperationComponent.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selecttoken/OnrampOperationComponent.kt @@ -18,12 +18,7 @@ internal interface OnrampOperationComponent : ComposableContentComponent { * Params * * @property operation operation - * @property hasSearchBar flag that indicates if search bar should be shown * @property userWalletId id of multi-currency wallet */ - data class Params( - val operation: OnrampOperation, - val hasSearchBar: Boolean, - val userWalletId: UserWalletId, - ) + data class Params(val operation: OnrampOperation, val userWalletId: UserWalletId) } \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/sell/DefaultSellCryptoComponent.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/sell/DefaultSellCryptoComponent.kt index ffba48a738..f7d63936d1 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/sell/DefaultSellCryptoComponent.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/sell/DefaultSellCryptoComponent.kt @@ -20,11 +20,7 @@ internal class DefaultSellCryptoComponent @AssistedInject constructor( private val selectTokenComponent: OnrampOperationComponent = onrampOperationComponentFactory.create( context = appComponentContext, - params = OnrampOperationComponent.Params( - operation = OnrampOperation.SELL, - hasSearchBar = true, - userWalletId = params.userWalletId, - ), + params = OnrampOperationComponent.Params(operation = OnrampOperation.SELL, userWalletId = params.userWalletId), ) @Composable diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/entity/ExchangeCardUM.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/entity/ExchangeCardUM.kt new file mode 100644 index 0000000000..07c60222b4 --- /dev/null +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/entity/ExchangeCardUM.kt @@ -0,0 +1,63 @@ +package com.tangem.features.onramp.swap.entity + +import com.tangem.core.ui.components.currency.icon.CurrencyIconState +import com.tangem.core.ui.components.token.state.TokenItemState +import com.tangem.core.ui.extensions.TextReference +import com.tangem.features.onramp.impl.R + +/** + * Exchange card UI model + * +[REDACTED_AUTHOR] + */ +internal sealed interface ExchangeCardUM { + + /** Title reference */ + val titleReference: TextReference + + /** Flag that indicates if remove button should be shown */ + val hasRemoveButton: Boolean + + /** Token item state */ + val tokenItemState: TokenItemState + + /** + * Empty state + * + * @property titleReference title reference + * @property onItemClick callback which will be called when an item is clicked + */ + data class Empty( + override val titleReference: TextReference, + val onItemClick: () -> Unit, + ) : ExchangeCardUM { + + override val hasRemoveButton: Boolean = false + + // TODO: [REDACTED_JIRA] + override val tokenItemState: TokenItemState = TokenItemState.Content( + id = "empty", + iconState = CurrencyIconState.Empty(R.drawable.ic_empty_64), + titleState = TokenItemState.TitleState.Content(text = "Choose the Token"), + subtitleState = TokenItemState.SubtitleState.TextContent(value = "You want to Swap"), + fiatAmountState = TokenItemState.FiatAmountState.Content(text = ""), + subtitle2State = TokenItemState.Subtitle2State.TextContent(text = ""), + onItemClick = onItemClick, + onItemLongClick = null, + ) + } + + /** + * Filled + * + * @property titleReference title reference + * @property tokenItemState token item state + */ + data class Filled( + override val titleReference: TextReference, + override val tokenItemState: TokenItemState, + ) : ExchangeCardUM { + + override val hasRemoveButton: Boolean = true + } +} \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/ui/ExchangeCard.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/ui/ExchangeCard.kt new file mode 100644 index 0000000000..4b1ce4c245 --- /dev/null +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/ui/ExchangeCard.kt @@ -0,0 +1,124 @@ +package com.tangem.features.onramp.swap.ui + +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.heightIn +import androidx.compose.material3.Text +import androidx.compose.material3.ripple +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.tooling.preview.PreviewParameter +import androidx.compose.ui.tooling.preview.PreviewParameterProvider +import androidx.compose.ui.unit.dp +import com.tangem.core.ui.components.currency.icon.CurrencyIconState +import com.tangem.core.ui.components.marketprice.PriceChangeType +import com.tangem.core.ui.components.rows.NetworkTitle +import com.tangem.core.ui.components.token.TokenItem +import com.tangem.core.ui.components.token.state.TokenItemState +import com.tangem.core.ui.extensions.resolveReference +import com.tangem.core.ui.extensions.resourceReference +import com.tangem.core.ui.res.TangemTheme +import com.tangem.core.ui.res.TangemThemePreview +import com.tangem.features.onramp.impl.R +import com.tangem.features.onramp.swap.entity.ExchangeCardUM + +/** + * Exchange card + * + * @param state state + * @param modifier modifier + * +[REDACTED_AUTHOR] + */ +@Composable +internal fun ExchangeCard(state: ExchangeCardUM, modifier: Modifier = Modifier) { + Column( + modifier = modifier + .fillMaxWidth() + .heightIn(min = 116.dp) + .background( + color = TangemTheme.colors.background.primary, + shape = TangemTheme.shapes.roundedCornersXMedium, + ), + verticalArrangement = Arrangement.SpaceBetween, + ) { + NetworkTitle( + title = { + Text( + text = state.titleReference.resolveReference(), + color = TangemTheme.colors.text.tertiary, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + style = TangemTheme.typography.subtitle2, + ) + }, + action = { + RemoveButton(hasRemoveButton = state.hasRemoveButton, onClick = {}) + }, + ) + + TokenItem(state = state.tokenItemState, isBalanceHidden = false) + } +} + +@Composable +private fun RemoveButton(hasRemoveButton: Boolean, onClick: () -> Unit) { + AnimatedVisibility(visible = hasRemoveButton) { + Text( + text = stringResource(id = R.string.manage_tokens_remove), + modifier = Modifier.clickable( + indication = ripple(bounded = false), + interactionSource = remember { MutableInteractionSource() }, + onClick = onClick, + ), + color = TangemTheme.colors.text.accent, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + style = TangemTheme.typography.body2, + ) + } +} + +@Preview +@Composable +private fun Preview_ExchangeCard(@PreviewParameter(ExchangeCardUMProvider::class) state: ExchangeCardUM) { + TangemThemePreview { + ExchangeCard(state = state) + } +} + +private class ExchangeCardUMProvider : PreviewParameterProvider { + + override val values: Sequence = sequenceOf( + ExchangeCardUM.Empty( + titleReference = resourceReference(id = R.string.swapping_from_title), + onItemClick = {}, + ), + ExchangeCardUM.Filled( + titleReference = resourceReference(id = R.string.swapping_from_title), + tokenItemState = TokenItemState.Content( + id = "1", + iconState = CurrencyIconState.Locked, + titleState = TokenItemState.TitleState.Content(text = "Bitcoin"), + fiatAmountState = TokenItemState.FiatAmountState.Content(text = "12 368,14 \$"), + subtitle2State = TokenItemState.Subtitle2State.TextContent(text = "0,35853044 BTC"), + subtitleState = TokenItemState.SubtitleState.CryptoPriceContent( + price = "34 496,75 \$", + priceChangePercent = "0,43 %", + type = PriceChangeType.DOWN, + ), + onItemClick = {}, + onItemLongClick = {}, + ), + ), + ) +} \ No newline at end of file From f76dcdd658e5b86f2213da45050a0ffbf067ce79 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 31 Oct 2024 17:43:09 +0500 Subject: [PATCH 066/106] Updated on 2026-08-14 --- gradle/dependencies.toml | 2 +- .../configurations/extension/AppExtensionConfigurations.kt | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/gradle/dependencies.toml b/gradle/dependencies.toml index 85a683a170..ecf6a68855 100644 --- a/gradle/dependencies.toml +++ b/gradle/dependencies.toml @@ -88,7 +88,7 @@ markdownComposeView = "0.5.4" # endregion Other libraries # region Tangem -tangemBlockchainSdk = "develop-845" +tangemBlockchainSdk = "develop-848" #tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds tangemCardSdk = "develop-395" #tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^ diff --git a/plugins/configuration/src/main/kotlin/com/tangem/plugin/configuration/configurations/extension/AppExtensionConfigurations.kt b/plugins/configuration/src/main/kotlin/com/tangem/plugin/configuration/configurations/extension/AppExtensionConfigurations.kt index 58ea63d4a6..fb3e3e15a1 100644 --- a/plugins/configuration/src/main/kotlin/com/tangem/plugin/configuration/configurations/extension/AppExtensionConfigurations.kt +++ b/plugins/configuration/src/main/kotlin/com/tangem/plugin/configuration/configurations/extension/AppExtensionConfigurations.kt @@ -39,7 +39,6 @@ private fun AppExtension.configureDefaultConfig(project: Project) { testInstrumentationRunner = "com.tangem.common.HiltTestRunner" } - } // TODO: [REDACTED_JIRA] @@ -105,6 +104,8 @@ private fun AppExtension.configurePackagingOptions() { excludes += "lib/x86_64/freebsd/libscrypt.so" excludes += "lib/x86_64/linux/libscrypt.so" excludes += "META-INF/gradle/incremental.annotation.processors" + + merges += "paymentrequest.proto" } } } \ No newline at end of file From b2f3753b1765bbb9ad629a9d1b8716171b23c901 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 31 Oct 2024 15:55:14 +0500 Subject: [PATCH 067/106] Updated on 2026-08-14 --- .../format/bigdecimal/BigDecimalFiatFormat.kt | 2 +- .../converters/YieldBalancesConverter.kt | 19 +++--- .../TokenDetailsLoadedBalanceConverter.kt | 63 ++++++++++--------- 3 files changed, 44 insertions(+), 40 deletions(-) diff --git a/core/ui/src/main/java/com/tangem/core/ui/format/bigdecimal/BigDecimalFiatFormat.kt b/core/ui/src/main/java/com/tangem/core/ui/format/bigdecimal/BigDecimalFiatFormat.kt index 4822754075..1834e10f45 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/format/bigdecimal/BigDecimalFiatFormat.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/format/bigdecimal/BigDecimalFiatFormat.kt @@ -12,7 +12,7 @@ open class BigDecimalFiatFormat( val fiatCurrencySymbol: String, val locale: Locale = Locale.getDefault(), ) : BigDecimalFormat { - override fun invoke(p1: BigDecimal): String = error("") + override fun invoke(value: BigDecimal): String = defaultAmount()(value) } // == Initializers == diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/converters/YieldBalancesConverter.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/converters/YieldBalancesConverter.kt index b618387124..e49a87d61a 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/converters/YieldBalancesConverter.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/converters/YieldBalancesConverter.kt @@ -2,12 +2,13 @@ package com.tangem.features.staking.impl.presentation.state.converters import com.tangem.common.extensions.isZero import com.tangem.core.ui.format.bigdecimal.crypto +import com.tangem.core.ui.format.bigdecimal.fiat import com.tangem.core.ui.format.bigdecimal.format -import com.tangem.core.ui.utils.BigDecimalFormatter import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.staking.model.stakekit.* import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.features.staking.impl.presentation.state.InnerYieldBalanceState +import com.tangem.lib.crypto.BlockchainUtils.isBSC import com.tangem.lib.crypto.BlockchainUtils.isSolana import com.tangem.utils.Provider import com.tangem.utils.converter.Converter @@ -44,11 +45,12 @@ internal class YieldBalancesConverter( val (type, isActionable) = getRewardBlockType() InnerYieldBalanceState.Data( rewardsCrypto = cryptoRewardsValue.format { crypto(cryptoCurrency) }, - rewardsFiat = BigDecimalFormatter.formatFiatAmount( - fiatAmount = fiatRewardsValue, - fiatCurrencyCode = appCurrency.code, - fiatCurrencySymbol = appCurrency.symbol, - ), + rewardsFiat = fiatRewardsValue.format { + fiat( + fiatCurrencyCode = appCurrency.code, + fiatCurrencySymbol = appCurrency.symbol, + ) + }, rewardBlockType = type, isActionable = isActionable, balance = balanceToShowItems.mapBalances(), @@ -67,6 +69,7 @@ internal class YieldBalancesConverter( private fun getRewardBlockType(): Pair { val cryptoCurrencyStatus = cryptoCurrencyStatusProvider() + val networkId = cryptoCurrencyStatus.currency.network.id.value val yieldBalance = cryptoCurrencyStatus.value.yieldBalance as? YieldBalance.Data val rewards = yieldBalance?.balance?.items ?.filter { it.type == BalanceType.REWARDS && !it.amount.isZero() } @@ -74,10 +77,8 @@ internal class YieldBalancesConverter( val isActionable = rewards?.any { it.pendingActions.isNotEmpty() } == true val isRewardsClaimable = rewards?.isNotEmpty() == true - val isSolana = isSolana(cryptoCurrencyStatus.currency.network.id.value) - return when { - isSolana -> RewardBlockType.RewardUnavailable to false + isSolana(networkId) || isBSC(networkId) -> RewardBlockType.RewardUnavailable to false isRewardsClaimable -> RewardBlockType.Rewards to isActionable else -> RewardBlockType.NoRewards to false } diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsLoadedBalanceConverter.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsLoadedBalanceConverter.kt index ae45ac9884..e7766f15e8 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsLoadedBalanceConverter.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsLoadedBalanceConverter.kt @@ -10,10 +10,7 @@ import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.extensions.resourceReference import com.tangem.core.ui.extensions.stringReference import com.tangem.core.ui.extensions.wrappedList -import com.tangem.core.ui.format.bigdecimal.crypto -import com.tangem.core.ui.format.bigdecimal.format -import com.tangem.core.ui.format.bigdecimal.percent -import com.tangem.core.ui.utils.BigDecimalFormatter +import com.tangem.core.ui.format.bigdecimal.* import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.staking.model.StakingAvailability import com.tangem.domain.staking.model.StakingEntryInfo @@ -29,8 +26,10 @@ import com.tangem.feature.tokendetails.presentation.tokendetails.state.utils.get import com.tangem.feature.tokendetails.presentation.tokendetails.viewmodels.TokenDetailsClickIntents import com.tangem.features.staking.api.featuretoggles.StakingFeatureToggles import com.tangem.features.tokendetails.impl.R +import com.tangem.lib.crypto.BlockchainUtils.isBSC import com.tangem.lib.crypto.BlockchainUtils.isSolana import com.tangem.utils.Provider +import com.tangem.utils.StringsSigns.DASH_SIGN import com.tangem.utils.converter.Converter import com.tangem.utils.isNullOrZero import kotlinx.collections.immutable.persistentListOf @@ -246,11 +245,12 @@ internal class TokenDetailsLoadedBalanceConverter( stakingCryptoAmount.format { crypto(symbol = symbol, decimals = decimals) }, ), fiatValue = stringReference( - BigDecimalFormatter.formatFiatAmount( - stakingFiatAmount, - appCurrencyProvider().code, - appCurrencyProvider().symbol, - ), + stakingFiatAmount.format { + fiat( + appCurrencyProvider().code, + appCurrencyProvider().symbol, + ) + }, ), rewardValue = getRewardText(status, stakingRewardAmount), onStakeClicked = clickIntents::onStakeBannerClick, @@ -273,19 +273,20 @@ internal class TokenDetailsLoadedBalanceConverter( } private fun formatPriceChange(status: CryptoCurrencyStatus.Value): String { - val priceChange = status.priceChange ?: return BigDecimalFormatter.EMPTY_BALANCE_SIGN + val priceChange = status.priceChange ?: return DASH_SIGN return priceChange.format { percent() } } private fun formatPrice(status: CryptoCurrencyStatus.Value, appCurrency: AppCurrency): String { - val fiatRate = status.fiatRate ?: return BigDecimalFormatter.EMPTY_BALANCE_SIGN + val fiatRate = status.fiatRate ?: return DASH_SIGN - return BigDecimalFormatter.formatFiatAmountUncapped( - fiatAmount = fiatRate, - fiatCurrencyCode = appCurrency.code, - fiatCurrencySymbol = appCurrency.symbol, - ) + return fiatRate.format { + fiat( + fiatCurrencyCode = appCurrency.code, + fiatCurrencySymbol = appCurrency.symbol, + ).uncapped() + } } private fun formatFiatAmount( @@ -294,14 +295,15 @@ internal class TokenDetailsLoadedBalanceConverter( selectedBalanceType: BalanceType, appCurrency: AppCurrency, ): String { - val fiatAmount = status.fiatAmount ?: return BigDecimalFormatter.EMPTY_BALANCE_SIGN + val fiatAmount = status.fiatAmount ?: return DASH_SIGN val totalAmount = fiatAmount.getBalance(selectedBalanceType, stakingFiatAmount) - return BigDecimalFormatter.formatFiatAmount( - fiatAmount = totalAmount, - fiatCurrencyCode = appCurrency.code, - fiatCurrencySymbol = appCurrency.symbol, - ) + return totalAmount.format { + fiat( + fiatCurrencyCode = appCurrency.code, + fiatCurrencySymbol = appCurrency.symbol, + ) + } } private fun formatCryptoAmount( @@ -309,16 +311,16 @@ internal class TokenDetailsLoadedBalanceConverter( stakingCryptoAmount: BigDecimal?, selectedBalanceType: BalanceType, ): String { - val amount = status.value.amount ?: return BigDecimalFormatter.EMPTY_BALANCE_SIGN + val amount = status.value.amount ?: return DASH_SIGN val totalAmount = amount.getBalance(selectedBalanceType, stakingCryptoAmount) return totalAmount.format { crypto(status.currency) } } private fun getRewardText(status: CryptoCurrencyStatus, stakingRewardAmount: BigDecimal?): TextReference { - val isSolana = isSolana(status.currency.network.id.value) + val networkId = status.currency.network.id.value val rewardBlockType = when { - isSolana -> RewardBlockType.RewardUnavailable + isSolana(networkId) || isBSC(networkId) -> RewardBlockType.RewardUnavailable stakingRewardAmount.isNullOrZero() -> RewardBlockType.NoRewards else -> RewardBlockType.Rewards } @@ -327,11 +329,12 @@ internal class TokenDetailsLoadedBalanceConverter( RewardBlockType.Rewards -> resourceReference( R.string.staking_details_rewards_to_claim, wrappedList( - BigDecimalFormatter.formatFiatAmount( - stakingRewardAmount, - appCurrencyProvider().code, - appCurrencyProvider().symbol, - ), + stakingRewardAmount.format { + fiat( + appCurrencyProvider().code, + appCurrencyProvider().symbol, + ) + }, ), ) RewardBlockType.NoRewards -> resourceReference(R.string.staking_details_no_rewards_to_claim) From 4594602447b485bb5812e0edaaa22857dfab3380 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 31 Oct 2024 17:59:34 +0500 Subject: [PATCH 068/106] Updated on 2026-08-14 --- .../converters/YieldBalancesConverter.kt | 4 +- .../TokenDetailsLoadedBalanceConverter.kt | 4 +- .../com/tangem/lib/crypto/BlockchainUtils.kt | 58 +++++++++---------- 3 files changed, 33 insertions(+), 33 deletions(-) diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/converters/YieldBalancesConverter.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/converters/YieldBalancesConverter.kt index e49a87d61a..cb9490bdc8 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/converters/YieldBalancesConverter.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/converters/YieldBalancesConverter.kt @@ -69,7 +69,7 @@ internal class YieldBalancesConverter( private fun getRewardBlockType(): Pair { val cryptoCurrencyStatus = cryptoCurrencyStatusProvider() - val networkId = cryptoCurrencyStatus.currency.network.id.value + val blockchainId = cryptoCurrencyStatus.currency.network.id.value val yieldBalance = cryptoCurrencyStatus.value.yieldBalance as? YieldBalance.Data val rewards = yieldBalance?.balance?.items ?.filter { it.type == BalanceType.REWARDS && !it.amount.isZero() } @@ -78,7 +78,7 @@ internal class YieldBalancesConverter( val isRewardsClaimable = rewards?.isNotEmpty() == true return when { - isSolana(networkId) || isBSC(networkId) -> RewardBlockType.RewardUnavailable to false + isSolana(blockchainId) || isBSC(blockchainId) -> RewardBlockType.RewardUnavailable to false isRewardsClaimable -> RewardBlockType.Rewards to isActionable else -> RewardBlockType.NoRewards to false } diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsLoadedBalanceConverter.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsLoadedBalanceConverter.kt index e7766f15e8..676cff23f2 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsLoadedBalanceConverter.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsLoadedBalanceConverter.kt @@ -318,9 +318,9 @@ internal class TokenDetailsLoadedBalanceConverter( } private fun getRewardText(status: CryptoCurrencyStatus, stakingRewardAmount: BigDecimal?): TextReference { - val networkId = status.currency.network.id.value + val blockchainId = status.currency.network.id.value val rewardBlockType = when { - isSolana(networkId) || isBSC(networkId) -> RewardBlockType.RewardUnavailable + isSolana(blockchainId) || isBSC(blockchainId) -> RewardBlockType.RewardUnavailable stakingRewardAmount.isNullOrZero() -> RewardBlockType.NoRewards else -> RewardBlockType.Rewards } diff --git a/libs/crypto/src/main/java/com/tangem/lib/crypto/BlockchainUtils.kt b/libs/crypto/src/main/java/com/tangem/lib/crypto/BlockchainUtils.kt index fe67dcf443..c2205435c2 100644 --- a/libs/crypto/src/main/java/com/tangem/lib/crypto/BlockchainUtils.kt +++ b/libs/crypto/src/main/java/com/tangem/lib/crypto/BlockchainUtils.kt @@ -21,8 +21,8 @@ object BlockchainUtils { private const val XRP_X_ADDRESS = 'X' /** Decodes XRP Blockchain address */ - fun decodeRippleXAddress(xAddress: String, networkId: String): XrpTaggedAddress? { - return if (networkId == Blockchain.XRP.id && xAddress.firstOrNull() == XRP_X_ADDRESS) { + fun decodeRippleXAddress(xAddress: String, blockchainId: String): XrpTaggedAddress? { + return if (blockchainId == Blockchain.XRP.id && xAddress.firstOrNull() == XRP_X_ADDRESS) { val decodedAddress = XrpAddressService.decodeXAddress(xAddress) return decodedAddress?.let(XrpTaggedAddressConverter()::convert) } else { @@ -31,65 +31,65 @@ object BlockchainUtils { } /** If current [networkId] is Bitcoin */ - fun isBitcoin(networkId: String): Boolean { - val blockchain = Blockchain.fromId(networkId) + fun isBitcoin(blockchainId: String): Boolean { + val blockchain = Blockchain.fromId(blockchainId) return blockchain == Blockchain.Bitcoin || blockchain == Blockchain.BitcoinTestnet } - /** If current [networkId] is Tezos */ - fun isTezos(networkId: String): Boolean { - val blockchain = Blockchain.fromId(networkId) + /** If current [blockchainId] is Tezos */ + fun isTezos(blockchainId: String): Boolean { + val blockchain = Blockchain.fromId(blockchainId) return blockchain == Blockchain.Tezos } - fun isCardano(networkId: String): Boolean { - val blockchain = Blockchain.fromId(networkId) + fun isCardano(blockchainId: String): Boolean { + val blockchain = Blockchain.fromId(blockchainId) return blockchain == Blockchain.Cardano } - /** If current [networkId] is BeaconChain */ - fun isBeaconChain(networkId: String): Boolean { - val blockchain = Blockchain.fromId(networkId) + /** If current [blockchainId] is BeaconChain */ + fun isBeaconChain(blockchainId: String): Boolean { + val blockchain = Blockchain.fromId(blockchainId) return blockchain == Blockchain.Binance || blockchain == Blockchain.BinanceTestnet } - /** If current [networkId] is Polygon */ - fun isPolygonChain(networkId: String): Boolean { - val blockchain = Blockchain.fromId(networkId) + /** If current [blockchainId] is Polygon */ + fun isPolygonChain(blockchainId: String): Boolean { + val blockchain = Blockchain.fromId(blockchainId) return blockchain == Blockchain.Polygon || blockchain == Blockchain.PolygonTestnet } - fun isTron(networkId: String): Boolean { - val blockchain = Blockchain.fromId(networkId) + fun isTron(blockchainId: String): Boolean { + val blockchain = Blockchain.fromId(blockchainId) return blockchain == Blockchain.Tron || blockchain == Blockchain.TronTestnet } - fun isSupportedNetworkId(networkId: String): Boolean { - return Blockchain.fromNetworkId(networkId)?.isSupportedInApp() ?: false + fun isSupportedNetworkId(blockchainId: String): Boolean { + return Blockchain.fromNetworkId(blockchainId)?.isSupportedInApp() ?: false } - fun isArbitrum(networkId: String): Boolean { - val blockchain = Blockchain.fromId(networkId) + fun isArbitrum(blockchainId: String): Boolean { + val blockchain = Blockchain.fromId(blockchainId) return blockchain == Blockchain.Arbitrum } - fun isSolana(networkId: String): Boolean { - val blockchain = Blockchain.fromId(networkId) + fun isSolana(blockchainId: String): Boolean { + val blockchain = Blockchain.fromId(blockchainId) return blockchain == Blockchain.Solana } - fun isPolkadot(networkId: String): Boolean { - val blockchain = Blockchain.fromId(networkId) + fun isPolkadot(blockchainId: String): Boolean { + val blockchain = Blockchain.fromId(blockchainId) return blockchain == Blockchain.Polkadot || blockchain == Blockchain.PolkadotTestnet } - fun isCosmos(networkId: String): Boolean { - val blockchain = Blockchain.fromId(networkId) + fun isCosmos(blockchainId: String): Boolean { + val blockchain = Blockchain.fromId(blockchainId) return blockchain == Blockchain.Cosmos || blockchain == Blockchain.CosmosTestnet } - fun isBSC(networkId: String): Boolean { - val blockchain = Blockchain.fromId(networkId) + fun isBSC(blockchainId: String): Boolean { + val blockchain = Blockchain.fromId(blockchainId) return blockchain == Blockchain.BSC || blockchain == Blockchain.BSCTestnet } From b98bc113fc0d266da51254d2fdda67cd0898cff9 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 1 Nov 2024 08:49:59 +0300 Subject: [PATCH 069/106] Updated on 2026-08-14 --- app/build.gradle.kts | 1 + .../main/java/com/tangem/tap/MainActivity.kt | 2 +- .../java/com/tangem/tap/di/ActivityModule.kt | 2 +- .../tangem/tap/di/TangemSdkManagerModule.kt | 2 +- .../com/tangem/tap/di/data/CardDataModule.kt | 2 +- .../tangem/tap/di/domain/CardDomainModule.kt | 2 +- .../tap/di/domain/SettingsDomainModule.kt | 2 +- .../DefaultDeleteSavedAccessCodesUseCase.kt | 2 +- .../card/DefaultDerivationsRepository.kt | 2 +- .../domain/card/DefaultResetCardUseCase.kt | 2 +- .../repository/DefaultScanCardRepository.kt | 2 +- .../sdk/impl/DefaultTangemSdkManager.kt | 3 +- .../domain/sdk/impl/MockTangemSdkManager.kt | 4 +- .../tap/domain/sdk/mocks/MockContent.kt | 2 +- .../tap/domain/sdk/mocks/MockProvider.kt | 2 +- .../sdk/mocks/content/NoteMockContent.kt | 2 +- .../sdk/mocks/content/Wallet2MockContent.kt | 2 +- .../sdk/mocks/content/WalletMockContent.kt | 2 +- .../DefaultLegacySettingsRepository.kt | 2 +- .../tasks/product/CreateProductWalletTask.kt | 21 +-- .../ui/cardsettings/CardSettingsViewModel.kt | 2 +- .../AccessCodeRecoveryViewModel.kt | 2 +- .../ui/securitymode/SecurityModeViewModel.kt | 2 +- .../wallet/redux/OnboardingWalletAction.kt | 4 +- .../redux/OnboardingWalletMiddleware.kt | 2 +- .../com/tangem/tap/proxy/AppStateHolder.kt | 2 +- features/onboarding-v2/impl/build.gradle.kts | 3 + .../features/onboarding/v2/TitleProvider.kt | 9 ++ .../impl/DefaultOnboardingEntryComponent.kt | 40 ++++- .../v2/entry/impl/di/ComponentModule.kt | 14 ++ .../entry/impl/model/OnboardingEntryModel.kt | 39 ++++- .../impl/routing/OnboardingChildFactory.kt | 13 +- .../v2/entry/impl/routing/OnboardingRoute.kt | 9 +- .../v2/entry/impl/ui/OnboardingEntry.kt | 49 +++--- .../api/OnboardingMultiWalletComponent.kt | 18 +++ .../DefaultOnboardingMultiWalletComponent.kt | 58 ++++++++ .../v2/multiwallet/impl/di/ComponentModule.kt | 33 +++++ .../v2/multiwallet/impl/model/Dialogs.kt | 16 ++ .../impl/model/OnboardingMultiWalletModel.kt | 140 ++++++++++++++++++ .../impl/model/OnboardingMultiWalletState.kt | 10 ++ .../v2/multiwallet/impl/model/Utils.kt | 13 ++ .../impl/ui/OnboardingMultiWallet.kt | 79 ++++++++++ .../impl/ui/state/OnboardingMultiWalletUM.kt | 21 +++ .../stepper/api/OnboardingStepperComponent.kt | 8 +- .../impl/DefaultOnboardingStepperComponent.kt | 20 --- .../api/OnboardingWallet12Component.kt | 13 -- .../DefaultOnboardingWallet12Component.kt | 46 ------ .../v2/wallet12/impl/di/ComponentModule.kt | 20 --- libs/tangem-sdk-api/.gitignore | 1 + libs/tangem-sdk-api/build.gradle.kts | 35 +++++ .../api/CreateProductWalletTaskResponse.kt | 24 +++ .../com/tangem/sdk/api}/TangemSdkManager.kt | 9 +- .../kotlin/com/tangem/sdk/api/TapErrors.kt | 49 ++++++ settings.gradle.kts | 1 + 54 files changed, 667 insertions(+), 198 deletions(-) create mode 100644 features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/TitleProvider.kt create mode 100644 features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/api/OnboardingMultiWalletComponent.kt create mode 100644 features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/DefaultOnboardingMultiWalletComponent.kt create mode 100644 features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/di/ComponentModule.kt create mode 100644 features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/model/Dialogs.kt create mode 100644 features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/model/OnboardingMultiWalletModel.kt create mode 100644 features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/model/OnboardingMultiWalletState.kt create mode 100644 features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/model/Utils.kt create mode 100644 features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/ui/OnboardingMultiWallet.kt create mode 100644 features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/ui/state/OnboardingMultiWalletUM.kt delete mode 100644 features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/wallet12/api/OnboardingWallet12Component.kt delete mode 100644 features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/wallet12/impl/DefaultOnboardingWallet12Component.kt delete mode 100644 features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/wallet12/impl/di/ComponentModule.kt create mode 100644 libs/tangem-sdk-api/.gitignore create mode 100644 libs/tangem-sdk-api/build.gradle.kts create mode 100644 libs/tangem-sdk-api/src/main/kotlin/com/tangem/sdk/api/CreateProductWalletTaskResponse.kt rename {app/src/main/java/com/tangem/tap/domain/sdk => libs/tangem-sdk-api/src/main/kotlin/com/tangem/sdk/api}/TangemSdkManager.kt (93%) create mode 100644 libs/tangem-sdk-api/src/main/kotlin/com/tangem/sdk/api/TapErrors.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 5d371ecb8e..9029856167 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -90,6 +90,7 @@ dependencies { implementation(projects.libs.crypto) implementation(projects.libs.auth) implementation(projects.libs.blockchainSdk) + implementation(projects.libs.tangemSdkApi) implementation(projects.data.appCurrency) implementation(projects.data.appTheme) diff --git a/app/src/main/java/com/tangem/tap/MainActivity.kt b/app/src/main/java/com/tangem/tap/MainActivity.kt index 3109a05bcd..cc4284cc7a 100644 --- a/app/src/main/java/com/tangem/tap/MainActivity.kt +++ b/app/src/main/java/com/tangem/tap/MainActivity.kt @@ -59,6 +59,7 @@ import com.tangem.features.staking.api.navigation.StakingRouter import com.tangem.features.tokendetails.navigation.TokenDetailsRouter import com.tangem.features.wallet.navigation.WalletRouter import com.tangem.operations.backup.BackupService +import com.tangem.sdk.api.TangemSdkManager import com.tangem.sdk.extensions.init import com.tangem.tap.common.ActivityResultCallbackHolder import com.tangem.tap.common.DialogManager @@ -68,7 +69,6 @@ import com.tangem.tap.common.apptheme.MutableAppThemeModeHolder import com.tangem.tap.common.extensions.dispatchNavigationAction import com.tangem.tap.common.extensions.showFragmentAllowingStateLoss import com.tangem.tap.common.redux.NotificationsHandler -import com.tangem.tap.domain.sdk.TangemSdkManager import com.tangem.tap.domain.walletconnect2.domain.WalletConnectInteractor import com.tangem.tap.features.intentHandler.IntentProcessor import com.tangem.tap.features.intentHandler.handlers.BackgroundScanIntentHandler diff --git a/app/src/main/java/com/tangem/tap/di/ActivityModule.kt b/app/src/main/java/com/tangem/tap/di/ActivityModule.kt index 62f28c043e..c8d8c5c2bf 100644 --- a/app/src/main/java/com/tangem/tap/di/ActivityModule.kt +++ b/app/src/main/java/com/tangem/tap/di/ActivityModule.kt @@ -6,8 +6,8 @@ import com.tangem.domain.exchange.RampStateManager import com.tangem.domain.tokens.GetPolkadotCheckHasImmortalUseCase import com.tangem.domain.tokens.GetPolkadotCheckHasResetUseCase import com.tangem.domain.tokens.repository.PolkadotAccountHealthCheckRepository +import com.tangem.sdk.api.TangemSdkManager import com.tangem.tap.domain.scanCard.repository.DefaultScanCardRepository -import com.tangem.tap.domain.sdk.TangemSdkManager import com.tangem.tap.network.exchangeServices.DefaultRampManager import com.tangem.tap.proxy.AppStateHolder import dagger.Module diff --git a/app/src/main/java/com/tangem/tap/di/TangemSdkManagerModule.kt b/app/src/main/java/com/tangem/tap/di/TangemSdkManagerModule.kt index c6118d1ac1..4754c5ec8e 100644 --- a/app/src/main/java/com/tangem/tap/di/TangemSdkManagerModule.kt +++ b/app/src/main/java/com/tangem/tap/di/TangemSdkManagerModule.kt @@ -3,9 +3,9 @@ package com.tangem.tap.di import android.content.Context import com.tangem.domain.card.BuildConfig import com.tangem.domain.card.repository.CardSdkConfigRepository +import com.tangem.sdk.api.TangemSdkManager import com.tangem.tap.domain.sdk.impl.DefaultTangemSdkManager import com.tangem.tap.domain.sdk.impl.MockTangemSdkManager -import com.tangem.tap.domain.sdk.TangemSdkManager import dagger.Module import dagger.Provides import dagger.hilt.InstallIn diff --git a/app/src/main/java/com/tangem/tap/di/data/CardDataModule.kt b/app/src/main/java/com/tangem/tap/di/data/CardDataModule.kt index b87e417fab..66d63389c9 100644 --- a/app/src/main/java/com/tangem/tap/di/data/CardDataModule.kt +++ b/app/src/main/java/com/tangem/tap/di/data/CardDataModule.kt @@ -2,7 +2,7 @@ package com.tangem.tap.di.data import com.tangem.datasource.local.userwallet.UserWalletsStore import com.tangem.domain.card.repository.DerivationsRepository -import com.tangem.tap.domain.sdk.TangemSdkManager +import com.tangem.sdk.api.TangemSdkManager import com.tangem.tap.domain.card.DefaultDerivationsRepository import com.tangem.utils.coroutines.CoroutineDispatcherProvider import dagger.Module diff --git a/app/src/main/java/com/tangem/tap/di/domain/CardDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/CardDomainModule.kt index 24c41445ab..55fb95de26 100644 --- a/app/src/main/java/com/tangem/tap/di/domain/CardDomainModule.kt +++ b/app/src/main/java/com/tangem/tap/di/domain/CardDomainModule.kt @@ -9,9 +9,9 @@ import com.tangem.domain.demo.IsDemoCardUseCase import com.tangem.domain.walletmanager.WalletManagersFacade import com.tangem.domain.wallets.legacy.UserWalletsListManager import com.tangem.domain.wallets.usecase.IsNeedToBackupUseCase +import com.tangem.sdk.api.TangemSdkManager import com.tangem.tap.domain.card.DefaultDeleteSavedAccessCodesUseCase import com.tangem.tap.domain.card.DefaultResetCardUseCase -import com.tangem.tap.domain.sdk.TangemSdkManager import dagger.Module import dagger.Provides import dagger.hilt.InstallIn diff --git a/app/src/main/java/com/tangem/tap/di/domain/SettingsDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/SettingsDomainModule.kt index 51ed0f3dd5..8ec26eb230 100644 --- a/app/src/main/java/com/tangem/tap/di/domain/SettingsDomainModule.kt +++ b/app/src/main/java/com/tangem/tap/di/domain/SettingsDomainModule.kt @@ -12,7 +12,7 @@ import com.tangem.domain.settings.repositories.PromoSettingsRepository import com.tangem.domain.settings.repositories.SettingsRepository import com.tangem.domain.settings.usercountry.FetchUserCountryUseCase import com.tangem.domain.settings.usercountry.GetUserCountryUseCase -import com.tangem.tap.domain.sdk.TangemSdkManager +import com.tangem.sdk.api.TangemSdkManager import com.tangem.tap.domain.settings.DefaultLegacySettingsRepository import dagger.Module import dagger.Provides diff --git a/app/src/main/java/com/tangem/tap/domain/card/DefaultDeleteSavedAccessCodesUseCase.kt b/app/src/main/java/com/tangem/tap/domain/card/DefaultDeleteSavedAccessCodesUseCase.kt index 8834604935..9a89db05b8 100644 --- a/app/src/main/java/com/tangem/tap/domain/card/DefaultDeleteSavedAccessCodesUseCase.kt +++ b/app/src/main/java/com/tangem/tap/domain/card/DefaultDeleteSavedAccessCodesUseCase.kt @@ -6,7 +6,7 @@ import arrow.core.right import com.tangem.common.doOnFailure import com.tangem.common.doOnSuccess import com.tangem.domain.card.DeleteSavedAccessCodesUseCase -import com.tangem.tap.domain.sdk.TangemSdkManager +import com.tangem.sdk.api.TangemSdkManager internal class DefaultDeleteSavedAccessCodesUseCase( private val tangemSdkManager: TangemSdkManager, diff --git a/app/src/main/java/com/tangem/tap/domain/card/DefaultDerivationsRepository.kt b/app/src/main/java/com/tangem/tap/domain/card/DefaultDerivationsRepository.kt index 0013049c04..45cc75e9da 100644 --- a/app/src/main/java/com/tangem/tap/domain/card/DefaultDerivationsRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/card/DefaultDerivationsRepository.kt @@ -20,7 +20,7 @@ import com.tangem.domain.tokens.model.Network import com.tangem.domain.wallets.models.UserWallet import com.tangem.domain.wallets.models.UserWalletId import com.tangem.operations.derivation.ExtendedPublicKeysMap -import com.tangem.tap.domain.sdk.TangemSdkManager +import com.tangem.sdk.api.TangemSdkManager import com.tangem.tap.domain.tasks.UserWalletIdPreflightReadFilter import com.tangem.utils.coroutines.CoroutineDispatcherProvider import kotlinx.coroutines.withContext diff --git a/app/src/main/java/com/tangem/tap/domain/card/DefaultResetCardUseCase.kt b/app/src/main/java/com/tangem/tap/domain/card/DefaultResetCardUseCase.kt index 1e322aeaba..aec5e21d16 100644 --- a/app/src/main/java/com/tangem/tap/domain/card/DefaultResetCardUseCase.kt +++ b/app/src/main/java/com/tangem/tap/domain/card/DefaultResetCardUseCase.kt @@ -14,7 +14,7 @@ import com.tangem.domain.card.ResetCardUseCase import com.tangem.domain.card.ResetCardUserCodeParams import com.tangem.domain.card.models.ResetCardError import com.tangem.domain.wallets.models.UserWalletId -import com.tangem.tap.domain.sdk.TangemSdkManager +import com.tangem.sdk.api.TangemSdkManager internal class DefaultResetCardUseCase( private val tangemSdkManager: TangemSdkManager, diff --git a/app/src/main/java/com/tangem/tap/domain/scanCard/repository/DefaultScanCardRepository.kt b/app/src/main/java/com/tangem/tap/domain/scanCard/repository/DefaultScanCardRepository.kt index ee417278e9..1a4b50cc5a 100644 --- a/app/src/main/java/com/tangem/tap/domain/scanCard/repository/DefaultScanCardRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/scanCard/repository/DefaultScanCardRepository.kt @@ -3,7 +3,7 @@ package com.tangem.tap.domain.scanCard.repository import com.tangem.common.CompletionResult import com.tangem.domain.card.repository.ScanCardRepository import com.tangem.domain.models.scan.ScanResponse -import com.tangem.tap.domain.sdk.TangemSdkManager +import com.tangem.sdk.api.TangemSdkManager import com.tangem.tap.domain.scanCard.utils.ScanCardExceptionConverter // TODO: Move to the :data:card module diff --git a/app/src/main/java/com/tangem/tap/domain/sdk/impl/DefaultTangemSdkManager.kt b/app/src/main/java/com/tangem/tap/domain/sdk/impl/DefaultTangemSdkManager.kt index 12c23b6fec..59dea35070 100644 --- a/app/src/main/java/com/tangem/tap/domain/sdk/impl/DefaultTangemSdkManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/sdk/impl/DefaultTangemSdkManager.kt @@ -32,8 +32,9 @@ import com.tangem.operations.pins.SetUserCodeCommand import com.tangem.operations.preflightread.PreflightReadFilter import com.tangem.operations.usersetttings.SetUserCodeRecoveryAllowedTask import com.tangem.operations.wallet.CreateWalletResponse +import com.tangem.sdk.api.CreateProductWalletTaskResponse +import com.tangem.sdk.api.TangemSdkManager import com.tangem.tap.derivationsFinder -import com.tangem.tap.domain.sdk.TangemSdkManager import com.tangem.tap.domain.tasks.product.* import com.tangem.tap.domain.twins.CreateFirstTwinWalletTask import com.tangem.tap.domain.twins.CreateSecondTwinWalletTask diff --git a/app/src/main/java/com/tangem/tap/domain/sdk/impl/MockTangemSdkManager.kt b/app/src/main/java/com/tangem/tap/domain/sdk/impl/MockTangemSdkManager.kt index faad2661c4..471a9436e2 100644 --- a/app/src/main/java/com/tangem/tap/domain/sdk/impl/MockTangemSdkManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/sdk/impl/MockTangemSdkManager.kt @@ -20,9 +20,9 @@ import com.tangem.domain.wallets.models.UserWalletId import com.tangem.operations.derivation.DerivationTaskResponse import com.tangem.operations.preflightread.PreflightReadFilter import com.tangem.operations.wallet.CreateWalletResponse -import com.tangem.tap.domain.sdk.TangemSdkManager +import com.tangem.sdk.api.CreateProductWalletTaskResponse +import com.tangem.sdk.api.TangemSdkManager import com.tangem.tap.domain.sdk.mocks.MockProvider -import com.tangem.tap.domain.tasks.product.CreateProductWalletTaskResponse @Suppress("TooManyFunctions") class MockTangemSdkManager( diff --git a/app/src/main/java/com/tangem/tap/domain/sdk/mocks/MockContent.kt b/app/src/main/java/com/tangem/tap/domain/sdk/mocks/MockContent.kt index bb8ca7deab..3c5ab7756f 100644 --- a/app/src/main/java/com/tangem/tap/domain/sdk/mocks/MockContent.kt +++ b/app/src/main/java/com/tangem/tap/domain/sdk/mocks/MockContent.kt @@ -6,7 +6,7 @@ import com.tangem.domain.models.scan.CardDTO import com.tangem.domain.models.scan.ScanResponse import com.tangem.operations.derivation.DerivationTaskResponse import com.tangem.operations.wallet.CreateWalletResponse -import com.tangem.tap.domain.tasks.product.CreateProductWalletTaskResponse +import com.tangem.sdk.api.CreateProductWalletTaskResponse interface MockContent { diff --git a/app/src/main/java/com/tangem/tap/domain/sdk/mocks/MockProvider.kt b/app/src/main/java/com/tangem/tap/domain/sdk/mocks/MockProvider.kt index 3811371aa7..83df27ebb4 100644 --- a/app/src/main/java/com/tangem/tap/domain/sdk/mocks/MockProvider.kt +++ b/app/src/main/java/com/tangem/tap/domain/sdk/mocks/MockProvider.kt @@ -4,10 +4,10 @@ import com.tangem.common.CompletionResult import com.tangem.common.core.TangemError import com.tangem.common.core.TangemSdkError import com.tangem.domain.models.scan.ProductType +import com.tangem.sdk.api.CreateProductWalletTaskResponse import com.tangem.tap.domain.sdk.mocks.content.NoteMockContent import com.tangem.tap.domain.sdk.mocks.content.WalletMockContent import com.tangem.tap.domain.sdk.mocks.content.Wallet2MockContent -import com.tangem.tap.domain.tasks.product.CreateProductWalletTaskResponse object MockProvider { diff --git a/app/src/main/java/com/tangem/tap/domain/sdk/mocks/content/NoteMockContent.kt b/app/src/main/java/com/tangem/tap/domain/sdk/mocks/content/NoteMockContent.kt index de2da2c546..3c7a2ae6bc 100644 --- a/app/src/main/java/com/tangem/tap/domain/sdk/mocks/content/NoteMockContent.kt +++ b/app/src/main/java/com/tangem/tap/domain/sdk/mocks/content/NoteMockContent.kt @@ -8,8 +8,8 @@ import com.tangem.domain.models.scan.ScanResponse import com.tangem.operations.attestation.Attestation import com.tangem.operations.derivation.DerivationTaskResponse import com.tangem.operations.wallet.CreateWalletResponse +import com.tangem.sdk.api.CreateProductWalletTaskResponse import com.tangem.tap.domain.sdk.mocks.MockContent -import com.tangem.tap.domain.tasks.product.CreateProductWalletTaskResponse import java.util.Date object NoteMockContent : MockContent { diff --git a/app/src/main/java/com/tangem/tap/domain/sdk/mocks/content/Wallet2MockContent.kt b/app/src/main/java/com/tangem/tap/domain/sdk/mocks/content/Wallet2MockContent.kt index 4920acf082..93a6fcbeaa 100644 --- a/app/src/main/java/com/tangem/tap/domain/sdk/mocks/content/Wallet2MockContent.kt +++ b/app/src/main/java/com/tangem/tap/domain/sdk/mocks/content/Wallet2MockContent.kt @@ -13,8 +13,8 @@ import com.tangem.operations.backup.PrimaryCard import com.tangem.operations.derivation.DerivationTaskResponse import com.tangem.operations.derivation.ExtendedPublicKeysMap import com.tangem.operations.wallet.CreateWalletResponse +import com.tangem.sdk.api.CreateProductWalletTaskResponse import com.tangem.tap.domain.sdk.mocks.MockContent -import com.tangem.tap.domain.tasks.product.CreateProductWalletTaskResponse import java.util.Date object Wallet2MockContent : MockContent { diff --git a/app/src/main/java/com/tangem/tap/domain/sdk/mocks/content/WalletMockContent.kt b/app/src/main/java/com/tangem/tap/domain/sdk/mocks/content/WalletMockContent.kt index 6168bdda1f..22263344cf 100644 --- a/app/src/main/java/com/tangem/tap/domain/sdk/mocks/content/WalletMockContent.kt +++ b/app/src/main/java/com/tangem/tap/domain/sdk/mocks/content/WalletMockContent.kt @@ -13,8 +13,8 @@ import com.tangem.operations.backup.PrimaryCard import com.tangem.operations.derivation.DerivationTaskResponse import com.tangem.operations.derivation.ExtendedPublicKeysMap import com.tangem.operations.wallet.CreateWalletResponse +import com.tangem.sdk.api.CreateProductWalletTaskResponse import com.tangem.tap.domain.sdk.mocks.MockContent -import com.tangem.tap.domain.tasks.product.CreateProductWalletTaskResponse import java.util.Date object WalletMockContent : MockContent { diff --git a/app/src/main/java/com/tangem/tap/domain/settings/DefaultLegacySettingsRepository.kt b/app/src/main/java/com/tangem/tap/domain/settings/DefaultLegacySettingsRepository.kt index 6a84f8cd30..e074ddc8f8 100644 --- a/app/src/main/java/com/tangem/tap/domain/settings/DefaultLegacySettingsRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/settings/DefaultLegacySettingsRepository.kt @@ -1,7 +1,7 @@ package com.tangem.tap.domain.settings import com.tangem.domain.settings.repositories.LegacySettingsRepository -import com.tangem.tap.domain.sdk.TangemSdkManager +import com.tangem.sdk.api.TangemSdkManager internal class DefaultLegacySettingsRepository( private val tangemSdkManager: TangemSdkManager, diff --git a/app/src/main/java/com/tangem/tap/domain/tasks/product/CreateProductWalletTask.kt b/app/src/main/java/com/tangem/tap/domain/tasks/product/CreateProductWalletTask.kt index 699e7a527e..198afbd041 100644 --- a/app/src/main/java/com/tangem/tap/domain/tasks/product/CreateProductWalletTask.kt +++ b/app/src/main/java/com/tangem/tap/domain/tasks/product/CreateProductWalletTask.kt @@ -2,7 +2,6 @@ package com.tangem.tap.domain.tasks.product import com.tangem.blockchain.common.Blockchain import com.tangem.common.CompletionResult -import com.tangem.common.card.Card import com.tangem.common.card.EllipticCurve import com.tangem.common.card.FirmwareVersion import com.tangem.common.core.CardSession @@ -19,33 +18,15 @@ import com.tangem.domain.common.DerivationStyleProvider import com.tangem.domain.common.TapWorkarounds.isTestCard import com.tangem.domain.common.configs.CardConfig import com.tangem.domain.models.scan.CardDTO -import com.tangem.domain.models.scan.KeyWalletPublicKey -import com.tangem.operations.CommandResponse import com.tangem.operations.backup.PrimaryCard import com.tangem.operations.backup.StartPrimaryCardLinkingCommand import com.tangem.operations.derivation.DeriveMultipleWalletPublicKeysTask -import com.tangem.operations.derivation.ExtendedPublicKeysMap import com.tangem.operations.read.ReadWalletsListCommand import com.tangem.operations.wallet.CreateWalletTask +import com.tangem.sdk.api.CreateProductWalletTaskResponse import com.tangem.tap.features.demo.DemoHelper import com.tangem.operations.wallet.CreateWalletResponse as SdkCreateWalletResponse -data class CreateProductWalletTaskResponse( - val card: CardDTO, - val derivedKeys: Map = mapOf(), - val primaryCard: PrimaryCard? = null, -) : CommandResponse { - constructor( - card: Card, - derivedKeys: Map = mapOf(), - primaryCard: PrimaryCard? = null, - ) : this( - card = CardDTO(card), - derivedKeys = derivedKeys, - primaryCard = primaryCard, - ) -} - private data class CreateWalletResponse( val cardId: String, val wallet: CardDTO.Wallet, diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsViewModel.kt b/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsViewModel.kt index 9af2855279..8ede05a98a 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsViewModel.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsViewModel.kt @@ -18,13 +18,13 @@ import com.tangem.domain.models.scan.CardDTO import com.tangem.domain.models.scan.ScanResponse import com.tangem.domain.wallets.builder.UserWalletIdBuilder import com.tangem.domain.wallets.models.UserWalletId +import com.tangem.sdk.api.TangemSdkManager import com.tangem.tap.common.analytics.events.AnalyticsParam import com.tangem.tap.common.analytics.events.Settings import com.tangem.tap.common.extensions.dispatchDialogShow import com.tangem.tap.common.extensions.dispatchNavigationAction import com.tangem.tap.common.redux.AppDialog import com.tangem.tap.domain.extensions.signedHashesCount -import com.tangem.tap.domain.sdk.TangemSdkManager import com.tangem.tap.features.details.ui.cardsettings.domain.CardSettingsInteractor import com.tangem.tap.features.details.ui.common.utils.* import com.tangem.tap.features.onboarding.products.twins.redux.CreateTwinWalletMode diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/coderecovery/AccessCodeRecoveryViewModel.kt b/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/coderecovery/AccessCodeRecoveryViewModel.kt index d65fb74a54..1652506ca9 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/coderecovery/AccessCodeRecoveryViewModel.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/coderecovery/AccessCodeRecoveryViewModel.kt @@ -6,10 +6,10 @@ import com.tangem.common.doOnSuccess import com.tangem.common.routing.AppRouter import com.tangem.core.analytics.Analytics import com.tangem.domain.common.util.cardTypesResolver +import com.tangem.sdk.api.TangemSdkManager import com.tangem.tap.common.analytics.events.AnalyticsParam import com.tangem.tap.common.analytics.events.Settings import com.tangem.tap.common.extensions.dispatchNavigationAction -import com.tangem.tap.domain.sdk.TangemSdkManager import com.tangem.tap.features.details.ui.cardsettings.domain.CardSettingsInteractor import com.tangem.tap.features.details.ui.common.utils.isAccessCodeRecoveryEnabled import com.tangem.tap.store diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/securitymode/SecurityModeViewModel.kt b/app/src/main/java/com/tangem/tap/features/details/ui/securitymode/SecurityModeViewModel.kt index a97cb68bfe..a7f0b763b3 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/securitymode/SecurityModeViewModel.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/securitymode/SecurityModeViewModel.kt @@ -7,10 +7,10 @@ import com.tangem.common.core.TangemSdkError import com.tangem.common.routing.AppRouter import com.tangem.core.analytics.Analytics import com.tangem.domain.common.util.cardTypesResolver +import com.tangem.sdk.api.TangemSdkManager import com.tangem.tap.common.analytics.events.AnalyticsParam import com.tangem.tap.common.analytics.events.Settings import com.tangem.tap.common.extensions.dispatchNavigationAction -import com.tangem.tap.domain.sdk.TangemSdkManager import com.tangem.tap.features.details.redux.SecurityOption import com.tangem.tap.features.details.ui.cardsettings.domain.CardSettingsInteractor import com.tangem.tap.features.details.ui.common.utils.getAllowedSecurityOptions diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletAction.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletAction.kt index e7cc9589a4..0f29d53f73 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletAction.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletAction.kt @@ -2,11 +2,11 @@ package com.tangem.tap.features.onboarding.products.wallet.redux import android.net.Uri import com.tangem.common.CompletionResult -import com.tangem.domain.wallets.models.UserWalletId import com.tangem.common.card.Card +import com.tangem.domain.wallets.models.UserWalletId import com.tangem.feature.onboarding.data.model.CreateWalletResponse import com.tangem.feature.onboarding.presentation.wallet2.analytics.SeedPhraseSource -import com.tangem.tap.domain.tasks.product.CreateProductWalletTaskResponse +import com.tangem.sdk.api.CreateProductWalletTaskResponse import kotlinx.coroutines.CoroutineScope import org.rekotlin.Action diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt index 42ef53fa15..dc9e77a48d 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt @@ -28,13 +28,13 @@ import com.tangem.feature.onboarding.presentation.wallet2.analytics.SeedPhraseSo import com.tangem.feature.wallet.presentation.wallet.domain.BackupValidator import com.tangem.operations.attestation.OnlineCardVerifier import com.tangem.operations.backup.BackupService +import com.tangem.sdk.api.CreateProductWalletTaskResponse import com.tangem.tap.* import com.tangem.tap.common.analytics.events.AnalyticsParam import com.tangem.tap.common.analytics.events.Onboarding import com.tangem.tap.common.extensions.* import com.tangem.tap.common.redux.AppState import com.tangem.tap.common.redux.global.GlobalAction -import com.tangem.tap.domain.tasks.product.CreateProductWalletTaskResponse import com.tangem.tap.features.demo.DemoHelper import com.tangem.tap.features.home.redux.HomeAction import com.tangem.tap.features.onboarding.OnboardingDialog diff --git a/app/src/main/java/com/tangem/tap/proxy/AppStateHolder.kt b/app/src/main/java/com/tangem/tap/proxy/AppStateHolder.kt index a2c9dd483f..f626aa4238 100644 --- a/app/src/main/java/com/tangem/tap/proxy/AppStateHolder.kt +++ b/app/src/main/java/com/tangem/tap/proxy/AppStateHolder.kt @@ -5,11 +5,11 @@ import com.tangem.domain.models.scan.ScanResponse import com.tangem.domain.redux.ReduxStateHolder import com.tangem.domain.redux.StateDialog import com.tangem.domain.wallets.models.UserWallet +import com.tangem.sdk.api.TangemSdkManager import com.tangem.tap.common.extensions.dispatchDialogShow import com.tangem.tap.common.extensions.dispatchWithMain import com.tangem.tap.common.extensions.onUserWalletSelected import com.tangem.tap.common.redux.AppState -import com.tangem.tap.domain.sdk.TangemSdkManager import com.tangem.tap.network.exchangeServices.ExchangeService import org.rekotlin.Action import org.rekotlin.Store diff --git a/features/onboarding-v2/impl/build.gradle.kts b/features/onboarding-v2/impl/build.gradle.kts index 8cc0843478..25f02a083b 100644 --- a/features/onboarding-v2/impl/build.gradle.kts +++ b/features/onboarding-v2/impl/build.gradle.kts @@ -30,6 +30,7 @@ dependencies { implementation(projects.domain.core) /** Tangem libraries */ + implementation(projects.libs.tangemSdkApi) implementation(deps.tangem.card.core) implementation(deps.tangem.card.android) { exclude(module = "joda-time") @@ -40,12 +41,14 @@ dependencies { implementation(deps.lifecycle.runtime.ktx) /** Compose libraries */ + implementation(deps.compose.material) // to use buttons implementation(deps.compose.material3) implementation(deps.compose.animation) implementation(deps.compose.foundation) implementation(deps.compose.ui) implementation(deps.compose.ui.tooling) implementation(deps.compose.coil) + implementation(deps.decompose.ext.compose) /** Other libraries */ implementation(deps.kotlin.immutable.collections) diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/TitleProvider.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/TitleProvider.kt new file mode 100644 index 0000000000..58dc49d42d --- /dev/null +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/TitleProvider.kt @@ -0,0 +1,9 @@ +package com.tangem.features.onboarding.v2 + +import com.tangem.core.ui.extensions.TextReference +import kotlinx.coroutines.flow.StateFlow + +interface TitleProvider { + val currentTitle: StateFlow + fun changeTitle(text: TextReference) +} \ No newline at end of file diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/entry/impl/DefaultOnboardingEntryComponent.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/entry/impl/DefaultOnboardingEntryComponent.kt index 4766803dd8..ac06b7c4a5 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/entry/impl/DefaultOnboardingEntryComponent.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/entry/impl/DefaultOnboardingEntryComponent.kt @@ -1,7 +1,9 @@ package com.tangem.features.onboarding.v2.entry.impl import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier +import com.arkivanov.decompose.extensions.compose.jetpack.subscribeAsState import com.arkivanov.decompose.router.stack.ChildStack import com.arkivanov.decompose.router.stack.StackNavigation import com.arkivanov.decompose.router.stack.childStack @@ -12,16 +14,19 @@ import com.tangem.core.decompose.context.child import com.tangem.core.decompose.context.childByContext import com.tangem.core.decompose.model.getOrCreateModel import com.tangem.core.decompose.navigation.inner.InnerNavigationHolder -import com.tangem.core.ui.extensions.stringReference import com.tangem.features.onboarding.v2.entry.OnboardingEntryComponent import com.tangem.features.onboarding.v2.entry.impl.model.OnboardingEntryModel import com.tangem.features.onboarding.v2.entry.impl.routing.OnboardingChildFactory import com.tangem.features.onboarding.v2.entry.impl.routing.OnboardingRoute import com.tangem.features.onboarding.v2.entry.impl.ui.OnboardingEntry import com.tangem.features.onboarding.v2.stepper.api.OnboardingStepperComponent +import com.tangem.utils.coroutines.JobHolder +import com.tangem.utils.coroutines.saveIn import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject +import kotlinx.coroutines.flow.update +import kotlinx.coroutines.launch internal class DefaultOnboardingEntryComponent @AssistedInject constructor( @Assisted val context: AppComponentContext, @@ -30,6 +35,7 @@ internal class DefaultOnboardingEntryComponent @AssistedInject constructor( onboardingChildFactory: OnboardingChildFactory, ) : OnboardingEntryComponent, AppComponentContext by context { + private val innerNavigationLinkJobHolder = JobHolder() private val model: OnboardingEntryModel = getOrCreateModel(params) private val stepperComponent = stepperFactory.create( @@ -39,7 +45,7 @@ internal class DefaultOnboardingEntryComponent @AssistedInject constructor( initState = OnboardingStepperComponent.StepperState( currentStep = 0, steps = 0, - title = stringReference(""), + title = model.titleProvider.currentTitle.value, showProgress = false, ), popBack = { @@ -68,6 +74,10 @@ internal class DefaultOnboardingEntryComponent @AssistedInject constructor( }, ) + init { + linkToInnerNavigation() + } + private fun popInternal(onComplete: (Boolean) -> Unit) { val activeChild = innerStack.value.active.instance if (activeChild is InnerNavigationHolder) { @@ -77,16 +87,36 @@ internal class DefaultOnboardingEntryComponent @AssistedInject constructor( } } + private fun linkToInnerNavigation() { + innerStack.observe { stack -> + val activeChild = stack.active.instance + if (activeChild is InnerNavigationHolder) { + componentScope.launch { + activeChild.innerNavigation.state.collect { state -> + stepperComponent.state.update { + it.copy( + currentStep = state.stackSize, + steps = state.stackMaxSize ?: 0, + title = model.titleProvider.currentTitle.value, + showProgress = state.stackMaxSize != null, + ) + } + } + }.saveIn(innerNavigationLinkJobHolder) + } + } + } + @Composable override fun Content(modifier: Modifier) { + val innerStackState by innerStack.subscribeAsState() + OnboardingEntry( modifier = modifier, stepperContent = { modifierParam -> stepperComponent.Content(modifierParam) }, - content = { modifierParam -> - // TODO - }, + childStack = innerStackState, ) } diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/entry/impl/di/ComponentModule.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/entry/impl/di/ComponentModule.kt index f24d33ef05..1ea00b3585 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/entry/impl/di/ComponentModule.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/entry/impl/di/ComponentModule.kt @@ -1,11 +1,16 @@ package com.tangem.features.onboarding.v2.entry.impl.di +import com.tangem.core.decompose.di.DecomposeComponent +import com.tangem.core.decompose.model.Model import com.tangem.features.onboarding.v2.entry.OnboardingEntryComponent import com.tangem.features.onboarding.v2.entry.impl.DefaultOnboardingEntryComponent +import com.tangem.features.onboarding.v2.entry.impl.model.OnboardingEntryModel import dagger.Binds import dagger.Module import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent +import dagger.multibindings.ClassKey +import dagger.multibindings.IntoMap import javax.inject.Singleton @Module @@ -17,4 +22,13 @@ internal interface ComponentModule { fun bindOnboardingEntryComponent( factory: DefaultOnboardingEntryComponent.Factory, ): OnboardingEntryComponent.Factory +} + +@Module +@InstallIn(DecomposeComponent::class) +internal interface ModelModule { + @Binds + @IntoMap + @ClassKey(OnboardingEntryModel::class) + fun provideModel(model: OnboardingEntryModel): Model } \ No newline at end of file diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/entry/impl/model/OnboardingEntryModel.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/entry/impl/model/OnboardingEntryModel.kt index 8c4d8cb474..9a040ff17c 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/entry/impl/model/OnboardingEntryModel.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/entry/impl/model/OnboardingEntryModel.kt @@ -2,6 +2,12 @@ package com.tangem.features.onboarding.v2.entry.impl.model import com.tangem.core.decompose.di.ComponentScoped import com.tangem.core.decompose.model.Model +import com.tangem.core.decompose.model.ParamsContainer +import com.tangem.core.ui.extensions.TextReference +import com.tangem.core.ui.extensions.stringReference +import com.tangem.domain.models.scan.ProductType +import com.tangem.domain.models.scan.ScanResponse +import com.tangem.features.onboarding.v2.TitleProvider import com.tangem.features.onboarding.v2.entry.OnboardingEntryComponent import com.tangem.features.onboarding.v2.entry.impl.model.state.OnboardingState import com.tangem.features.onboarding.v2.entry.impl.routing.OnboardingRoute @@ -11,13 +17,42 @@ import javax.inject.Inject @ComponentScoped internal class OnboardingEntryModel @Inject constructor( + paramsContainer: ParamsContainer, override val dispatchers: CoroutineDispatcherProvider, - private val params: OnboardingEntryComponent.Params, ) : Model() { + private val params = paramsContainer.require() + + val titleProvider = object : TitleProvider { + override val currentTitle = MutableStateFlow(stringReference("")) + override fun changeTitle(text: TextReference) { + currentTitle.value = text + } + } + val state = MutableStateFlow( OnboardingState( - currentRoute = OnboardingRoute.Wallet12(params.scanResponse), + currentRoute = routeByProductType(params.scanResponse), ), ) + + private fun routeByProductType(scanResponse: ScanResponse): OnboardingRoute { + return when (scanResponse.productType) { + ProductType.Note -> TODO() + ProductType.Twins -> TODO() + ProductType.Wallet -> OnboardingRoute.Wallet12( + scanResponse = scanResponse, + withSeedPhraseFlow = false, + titleProvider = titleProvider, + ) + ProductType.Wallet2 -> OnboardingRoute.Wallet12( + scanResponse = scanResponse, + withSeedPhraseFlow = true, + titleProvider = titleProvider, + ) + ProductType.Start2Coin -> TODO() + ProductType.Ring -> TODO() + ProductType.Visa -> TODO() + } + } } \ No newline at end of file diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/entry/impl/routing/OnboardingChildFactory.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/entry/impl/routing/OnboardingChildFactory.kt index faf659c52e..3cd1b811a8 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/entry/impl/routing/OnboardingChildFactory.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/entry/impl/routing/OnboardingChildFactory.kt @@ -1,21 +1,24 @@ package com.tangem.features.onboarding.v2.entry.impl.routing import com.tangem.core.decompose.context.AppComponentContext -import com.tangem.features.onboarding.v2.wallet12.api.OnboardingWallet12Component +import com.tangem.features.onboarding.v2.multiwallet.api.OnboardingMultiWalletComponent import javax.inject.Inject internal class OnboardingChildFactory @Inject constructor( - private val wallet12ComponentFactory: OnboardingWallet12Component.Factory, + private val wallet12ComponentFactory: OnboardingMultiWalletComponent.Factory, ) { - fun createChild(route: OnboardingRoute, childContext: AppComponentContext) { - when (route) { + fun createChild(route: OnboardingRoute, childContext: AppComponentContext): Any { + return when (route) { is OnboardingRoute.Wallet12 -> wallet12ComponentFactory.create( context = childContext, - params = OnboardingWallet12Component.Params( + params = OnboardingMultiWalletComponent.Params( scanResponse = route.scanResponse, + withSeedPhraseFlow = route.withSeedPhraseFlow, + titleProvider = route.titleProvider, ), ) + else -> Unit } } } \ No newline at end of file diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/entry/impl/routing/OnboardingRoute.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/entry/impl/routing/OnboardingRoute.kt index 5b9847190a..4dbd6593f0 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/entry/impl/routing/OnboardingRoute.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/entry/impl/routing/OnboardingRoute.kt @@ -2,8 +2,15 @@ package com.tangem.features.onboarding.v2.entry.impl.routing import com.tangem.core.decompose.navigation.Route import com.tangem.domain.models.scan.ScanResponse +import com.tangem.features.onboarding.v2.TitleProvider sealed class OnboardingRoute : Route { - data class Wallet12(val scanResponse: ScanResponse) : OnboardingRoute() + data object None : OnboardingRoute() + + data class Wallet12( + val titleProvider: TitleProvider, + val scanResponse: ScanResponse, + val withSeedPhraseFlow: Boolean, + ) : OnboardingRoute() } \ No newline at end of file diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/entry/impl/ui/OnboardingEntry.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/entry/impl/ui/OnboardingEntry.kt index 3b8da8d889..16400dd642 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/entry/impl/ui/OnboardingEntry.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/entry/impl/ui/OnboardingEntry.kt @@ -4,19 +4,19 @@ import androidx.compose.foundation.background import androidx.compose.foundation.layout.* import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.tooling.preview.Preview -import com.tangem.core.ui.components.appbar.TangemTopAppBar -import com.tangem.core.ui.components.appbar.models.TopAppBarButtonUM +import com.arkivanov.decompose.extensions.compose.jetpack.stack.Children +import com.arkivanov.decompose.extensions.compose.jetpack.stack.animation.slide +import com.arkivanov.decompose.extensions.compose.jetpack.stack.animation.stackAnimation +import com.arkivanov.decompose.router.stack.ChildStack import com.tangem.core.ui.res.TangemTheme -import com.tangem.core.ui.res.TangemThemePreview -import com.tangem.features.onboarding.v2.impl.R +import com.tangem.features.onboarding.v2.entry.impl.routing.OnboardingRoute +import com.tangem.features.onboarding.v2.multiwallet.api.OnboardingMultiWalletComponent @Composable internal inline fun OnboardingEntry( modifier: Modifier = Modifier, + childStack: ChildStack, stepperContent: @Composable (Modifier) -> Unit, - content: @Composable (Modifier) -> Unit, ) { Column( modifier = modifier @@ -25,28 +25,19 @@ internal inline fun OnboardingEntry( .background(TangemTheme.colors.background.primary), ) { stepperContent(Modifier.fillMaxWidth()) - content(Modifier.fillMaxSize()) - } -} -@Preview -@Composable -private fun Preview() { - TangemThemePreview { - OnboardingEntry( - stepperContent = { - TangemTopAppBar( - title = "Onboarding", - startButton = TopAppBarButtonUM( - iconRes = R.drawable.ic_back_24, - enabled = true, - onIconClicked = {}, - ), - ) - }, - content = { modifier -> - Box(modifier.background(Color.Cyan.copy(alpha = 0.3f))) - }, - ) + Children( + stack = childStack, + animation = stackAnimation(slide()), + ) { + when (it.configuration) { + is OnboardingRoute.Wallet12 -> { + (it.instance as OnboardingMultiWalletComponent).Content( + modifier = modifier, + ) + } + OnboardingRoute.None -> {} + } + } } } \ No newline at end of file diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/api/OnboardingMultiWalletComponent.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/api/OnboardingMultiWalletComponent.kt new file mode 100644 index 0000000000..1dedfd3d41 --- /dev/null +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/api/OnboardingMultiWalletComponent.kt @@ -0,0 +1,18 @@ +package com.tangem.features.onboarding.v2.multiwallet.api + +import com.tangem.core.decompose.factory.ComponentFactory +import com.tangem.core.decompose.navigation.inner.InnerNavigationHolder +import com.tangem.core.ui.decompose.ComposableContentComponent +import com.tangem.domain.models.scan.ScanResponse +import com.tangem.features.onboarding.v2.TitleProvider + +interface OnboardingMultiWalletComponent : ComposableContentComponent, InnerNavigationHolder { + + data class Params( + val titleProvider: TitleProvider, + val scanResponse: ScanResponse, + val withSeedPhraseFlow: Boolean, + ) + + interface Factory : ComponentFactory +} \ No newline at end of file diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/DefaultOnboardingMultiWalletComponent.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/DefaultOnboardingMultiWalletComponent.kt new file mode 100644 index 0000000000..8cc7a9e553 --- /dev/null +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/DefaultOnboardingMultiWalletComponent.kt @@ -0,0 +1,58 @@ +package com.tangem.features.onboarding.v2.multiwallet.impl + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.ui.Modifier +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.tangem.core.decompose.context.AppComponentContext +import com.tangem.core.decompose.model.getOrCreateModel +import com.tangem.core.decompose.navigation.inner.InnerNavigation +import com.tangem.core.decompose.navigation.inner.InnerNavigationState +import com.tangem.features.onboarding.v2.multiwallet.api.OnboardingMultiWalletComponent +import com.tangem.features.onboarding.v2.multiwallet.impl.model.OnboardingMultiWalletModel +import com.tangem.features.onboarding.v2.multiwallet.impl.ui.OnboardingMultiWallet +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject +import kotlinx.coroutines.flow.MutableStateFlow + +internal class DefaultOnboardingMultiWalletComponent @AssistedInject constructor( + @Assisted private val context: AppComponentContext, + @Assisted private val params: OnboardingMultiWalletComponent.Params, +) : OnboardingMultiWalletComponent, AppComponentContext by context { + + private val model: OnboardingMultiWalletModel = getOrCreateModel(params) + + override val innerNavigation: InnerNavigation = object : InnerNavigation { + override val state = MutableStateFlow( + Wallet12InnerNavigationState(1, 5), // TODO + ) + + override fun pop(onComplete: (Boolean) -> Unit) { + // TODO + } + } + + @Composable + override fun Content(modifier: Modifier) { + val uiState by model.uiState.collectAsStateWithLifecycle() + + OnboardingMultiWallet( + modifier = modifier, + state = uiState, + ) + } + + @AssistedFactory + interface Factory : OnboardingMultiWalletComponent.Factory { + override fun create( + context: AppComponentContext, + params: OnboardingMultiWalletComponent.Params, + ): DefaultOnboardingMultiWalletComponent + } +} + +data class Wallet12InnerNavigationState( + override val stackSize: Int, + override val stackMaxSize: Int?, +) : InnerNavigationState \ No newline at end of file diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/di/ComponentModule.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/di/ComponentModule.kt new file mode 100644 index 0000000000..1c7fd39182 --- /dev/null +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/di/ComponentModule.kt @@ -0,0 +1,33 @@ +package com.tangem.features.onboarding.v2.multiwallet.impl.di + +import com.tangem.core.decompose.di.DecomposeComponent +import com.tangem.core.decompose.model.Model +import com.tangem.features.onboarding.v2.multiwallet.api.OnboardingMultiWalletComponent +import com.tangem.features.onboarding.v2.multiwallet.impl.DefaultOnboardingMultiWalletComponent +import com.tangem.features.onboarding.v2.multiwallet.impl.model.OnboardingMultiWalletModel +import dagger.Binds +import dagger.Module +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import dagger.multibindings.ClassKey +import dagger.multibindings.IntoMap +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +internal interface ComponentModule { + + @Binds + @Singleton + fun bindComponent(factory: DefaultOnboardingMultiWalletComponent.Factory): OnboardingMultiWalletComponent.Factory +} + +@Module +@InstallIn(DecomposeComponent::class) +internal interface ModelModule { + + @Binds + @IntoMap + @ClassKey(OnboardingMultiWalletModel::class) + fun provideModel(model: OnboardingMultiWalletModel): Model +} \ No newline at end of file diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/model/Dialogs.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/model/Dialogs.kt new file mode 100644 index 0000000000..f6905984f6 --- /dev/null +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/model/Dialogs.kt @@ -0,0 +1,16 @@ +package com.tangem.features.onboarding.v2.multiwallet.impl.model + +import com.tangem.core.ui.extensions.resourceReference +import com.tangem.features.onboarding.v2.impl.R +import com.tangem.features.onboarding.v2.multiwallet.impl.ui.state.OnboardingMultiWalletUM + +internal fun resetCardDialog(onConfirm: () -> Unit, onDismiss: () -> Unit, onDismissButtonClick: () -> Unit) = + OnboardingMultiWalletUM.Dialog( + title = resourceReference(R.string.onboarding_activation_error_title), + description = resourceReference(R.string.onboarding_activation_error_message), + confirmButtonText = resourceReference(R.string.common_ok), + dismissButtonText = resourceReference(R.string.common_support), + onDismiss = onDismiss, + onConfirm = onConfirm, + onDismissButtonClick = onDismissButtonClick, + ) \ No newline at end of file diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/model/OnboardingMultiWalletModel.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/model/OnboardingMultiWalletModel.kt new file mode 100644 index 0000000000..f031b2a3f6 --- /dev/null +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/model/OnboardingMultiWalletModel.kt @@ -0,0 +1,140 @@ +package com.tangem.features.onboarding.v2.multiwallet.impl.model + +import com.tangem.common.CompletionResult +import com.tangem.common.core.TangemSdkError +import com.tangem.core.decompose.di.ComponentScoped +import com.tangem.core.decompose.model.Model +import com.tangem.core.decompose.model.ParamsContainer +import com.tangem.domain.feedback.GetCardInfoUseCase +import com.tangem.domain.feedback.SendFeedbackEmailUseCase +import com.tangem.domain.feedback.models.FeedbackEmailType +import com.tangem.domain.models.scan.CardDTO +import com.tangem.features.onboarding.v2.multiwallet.api.OnboardingMultiWalletComponent +import com.tangem.features.onboarding.v2.multiwallet.impl.ui.state.OnboardingMultiWalletUM +import com.tangem.sdk.api.TangemSdkManager +import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.update +import kotlinx.coroutines.launch +import javax.inject.Inject + +@ComponentScoped +internal class OnboardingMultiWalletModel @Inject constructor( + paramsContainer: ParamsContainer, + override val dispatchers: CoroutineDispatcherProvider, + private val tangemSdkManager: TangemSdkManager, + private val sendFeedbackEmailUseCase: SendFeedbackEmailUseCase, + private val getCardInfoUseCase: GetCardInfoUseCase, +) : Model() { + + private val params = paramsContainer.require() + private val currentScanResponse = MutableStateFlow(params.scanResponse) + + val state = MutableStateFlow( + OnboardingMultiWalletState( + currentStep = getInitialStep(), + ), + ) + + val uiState = MutableStateFlow( + OnboardingMultiWalletUM( + onCreateWalletClick = { createWallet(false) }, + showSeedPhraseOption = params.withSeedPhraseFlow, + onOtherOptionsClick = { /* navigate */ }, + onBack = { }, + dialog = null, + ), + ) + + init { + initScreenTitleSub() + } + + private fun getInitialStep(): OnboardingMultiWalletState.Step { + val card = currentScanResponse.value.card + + return when { + card.wallets.isNotEmpty() && card.backupStatus == CardDTO.BackupStatus.NoBackup -> + OnboardingMultiWalletState.Step.AddBackupDevice + card.wallets.isNotEmpty() && card.backupStatus?.isActive == true -> + OnboardingMultiWalletState.Step.FinishBackup + else -> + OnboardingMultiWalletState.Step.CreateWallet + } + } + + private fun initScreenTitleSub() { + val title = screenTitleByStep(getInitialStep()) + params.titleProvider.changeTitle(title) + + modelScope.launch { + state + .map { it.currentStep } + .collectLatest { step -> + val title = screenTitleByStep(step) + params.titleProvider.changeTitle(title) + } + } + } + + private fun createWallet(shouldReset: Boolean) { + modelScope.launch { + val result = tangemSdkManager.createProductWallet( + scanResponse = currentScanResponse.value, + shouldReset = shouldReset, + ) + + when (result) { + is CompletionResult.Success -> { + currentScanResponse.update { + it.copy( + card = result.data.card, + derivedKeys = result.data.derivedKeys, + primaryCard = result.data.primaryCard, + ) + } + + // TODO + // Analytics.send(Onboarding.CreateWallet.WalletCreatedSuccessfully()) + } + + is CompletionResult.Failure -> { + if (result.error is TangemSdkError.WalletAlreadyCreated) { + // show should reset dialog + handleActivationError() + } + } + } + } + } + + private fun handleActivationError() { + uiState.update { + it.copy( + dialog = resetCardDialog( + onConfirm = { + uiState.update { it.copy(dialog = null) } + resetCard() + }, + onDismiss = { + uiState.update { it.copy(dialog = null) } + }, + onDismissButtonClick = ::navigateToSupportScreen, + ), + ) + } + } + + private fun resetCard() { + createWallet(true) + } + + fun navigateToSupportScreen() { + modelScope.launch { + val cardInfo = getCardInfoUseCase(currentScanResponse.value).getOrNull() ?: return@launch + sendFeedbackEmailUseCase(FeedbackEmailType.DirectUserRequest(cardInfo)) + } + } +} \ No newline at end of file diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/model/OnboardingMultiWalletState.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/model/OnboardingMultiWalletState.kt new file mode 100644 index 0000000000..d1b11653dd --- /dev/null +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/model/OnboardingMultiWalletState.kt @@ -0,0 +1,10 @@ +package com.tangem.features.onboarding.v2.multiwallet.impl.model + +data class OnboardingMultiWalletState( + val currentStep: Step, +) { + + enum class Step { + GeneratePrivateKeys, CreateWallet, AddBackupDevice, FinishBackup, Done + } +} \ No newline at end of file diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/model/Utils.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/model/Utils.kt new file mode 100644 index 0000000000..fb735811de --- /dev/null +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/model/Utils.kt @@ -0,0 +1,13 @@ +package com.tangem.features.onboarding.v2.multiwallet.impl.model + +import com.tangem.core.ui.extensions.TextReference +import com.tangem.core.ui.extensions.resourceReference +import com.tangem.features.onboarding.v2.impl.R + +fun screenTitleByStep(step: OnboardingMultiWalletState.Step): TextReference = when (step) { + OnboardingMultiWalletState.Step.GeneratePrivateKeys -> TODO() + OnboardingMultiWalletState.Step.CreateWallet -> resourceReference(R.string.onboarding_create_wallet_header) + OnboardingMultiWalletState.Step.AddBackupDevice -> TODO() + OnboardingMultiWalletState.Step.FinishBackup -> TODO() + OnboardingMultiWalletState.Step.Done -> TODO() +} \ No newline at end of file diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/ui/OnboardingMultiWallet.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/ui/OnboardingMultiWallet.kt new file mode 100644 index 0000000000..d2de011033 --- /dev/null +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/ui/OnboardingMultiWallet.kt @@ -0,0 +1,79 @@ +package com.tangem.features.onboarding.v2.multiwallet.impl.ui + +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.tangem.core.ui.components.BasicDialog +import com.tangem.core.ui.components.DialogButtonUM +import com.tangem.core.ui.components.PrimaryButtonIconEnd +import com.tangem.core.ui.components.SecondaryButton +import com.tangem.core.ui.extensions.resolveReference +import com.tangem.core.ui.res.TangemThemePreview +import com.tangem.features.onboarding.v2.impl.R +import com.tangem.features.onboarding.v2.multiwallet.impl.ui.state.OnboardingMultiWalletUM + +@Composable +internal fun OnboardingMultiWallet(state: OnboardingMultiWalletUM, modifier: Modifier = Modifier) { + if (state.dialog != null) { + BasicDialog( + title = state.dialog.title.resolveReference(), + message = state.dialog.description.resolveReference(), + confirmButton = DialogButtonUM( + title = state.dialog.confirmButtonText.resolveReference(), + onClick = state.dialog.onConfirm, + ), + dismissButton = DialogButtonUM( + title = state.dialog.dismissButtonText.resolveReference(), + onClick = state.dialog.onDismissButtonClick, + ), + onDismissDialog = state.dialog.onDismiss, + ) + } + + Column( + modifier = modifier + .fillMaxSize() + .navigationBarsPadding() + .verticalScroll(rememberScrollState()), + ) { + Box(Modifier.weight(1f)) + + PrimaryButtonIconEnd( + modifier = Modifier + .padding(start = 16.dp, end = 16.dp, bottom = 12.dp) + .fillMaxWidth(), + iconResId = R.drawable.ic_tangem_24, + text = stringResource(R.string.onboarding_create_wallet_button_create_wallet), + onClick = state.onCreateWalletClick, + ) + + SecondaryButton( + modifier = Modifier + .padding(start = 16.dp, end = 16.dp, bottom = 16.dp) + .fillMaxWidth(), + text = stringResource(R.string.onboarding_create_wallet_options_button_options), + onClick = state.onOtherOptionsClick, + ) + } +} + +@Preview +@Composable +private fun Preview() { + TangemThemePreview { + OnboardingMultiWallet( + state = OnboardingMultiWalletUM( + onCreateWalletClick = {}, + showSeedPhraseOption = true, + onBack = {}, + onOtherOptionsClick = {}, + dialog = null, + ), + ) + } +} \ No newline at end of file diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/ui/state/OnboardingMultiWalletUM.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/ui/state/OnboardingMultiWalletUM.kt new file mode 100644 index 0000000000..8ae99c8da0 --- /dev/null +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/ui/state/OnboardingMultiWalletUM.kt @@ -0,0 +1,21 @@ +package com.tangem.features.onboarding.v2.multiwallet.impl.ui.state + +import com.tangem.core.ui.extensions.TextReference + +internal data class OnboardingMultiWalletUM( + val onCreateWalletClick: () -> Unit, + val showSeedPhraseOption: Boolean, + val onOtherOptionsClick: () -> Unit, + val onBack: () -> Unit, + val dialog: Dialog?, +) { + data class Dialog( + val title: TextReference, + val description: TextReference, + val dismissButtonText: TextReference, + val confirmButtonText: TextReference, + val onConfirm: () -> Unit, + val onDismissButtonClick: () -> Unit, + val onDismiss: () -> Unit, + ) +} \ No newline at end of file diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/stepper/api/OnboardingStepperComponent.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/stepper/api/OnboardingStepperComponent.kt index 328deab736..8943397d3d 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/stepper/api/OnboardingStepperComponent.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/stepper/api/OnboardingStepperComponent.kt @@ -5,7 +5,7 @@ import com.tangem.core.decompose.factory.ComponentFactory import com.tangem.core.ui.decompose.ComposableContentComponent import com.tangem.core.ui.extensions.TextReference import com.tangem.domain.models.scan.ScanResponse -import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.MutableStateFlow internal interface OnboardingStepperComponent : ComposableContentComponent { @@ -22,11 +22,7 @@ internal interface OnboardingStepperComponent : ComposableContentComponent { val scanResponse: ScanResponse, ) - val state: StateFlow - - fun changeStep(step: Int) - fun nextStep() - fun changeTitle(title: TextReference) + val state: MutableStateFlow interface Factory : ComponentFactory } \ No newline at end of file diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/stepper/impl/DefaultOnboardingStepperComponent.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/stepper/impl/DefaultOnboardingStepperComponent.kt index 8e0bfb77ac..2f4253b9f6 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/stepper/impl/DefaultOnboardingStepperComponent.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/stepper/impl/DefaultOnboardingStepperComponent.kt @@ -7,7 +7,6 @@ import androidx.compose.ui.Modifier import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.arkivanov.essenty.instancekeeper.getOrCreateSimple import com.tangem.core.decompose.context.AppComponentContext -import com.tangem.core.ui.extensions.TextReference import com.tangem.domain.feedback.GetCardInfoUseCase import com.tangem.domain.feedback.SendFeedbackEmailUseCase import com.tangem.domain.feedback.models.FeedbackEmailType @@ -17,7 +16,6 @@ import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch internal class DefaultOnboardingStepperComponent @AssistedInject constructor( @@ -29,24 +27,6 @@ internal class DefaultOnboardingStepperComponent @AssistedInject constructor( override val state = instanceKeeper.getOrCreateSimple { MutableStateFlow(params.initState) } - override fun changeStep(step: Int) { - state.update { it.copy(currentStep = step.coerceAtLeast(minimumValue = 0)) } - } - - override fun nextStep() { - state.update { state -> - if (state.currentStep < state.steps - 1) { - state.copy(currentStep = state.currentStep + 1) - } else { - state - } - } - } - - override fun changeTitle(title: TextReference) { - state.update { it.copy(title = title) } - } - private fun openSupport() { componentScope.launch { val cardInfo = getCardInfoUseCase(params.scanResponse).getOrNull() ?: return@launch diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/wallet12/api/OnboardingWallet12Component.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/wallet12/api/OnboardingWallet12Component.kt deleted file mode 100644 index c950955961..0000000000 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/wallet12/api/OnboardingWallet12Component.kt +++ /dev/null @@ -1,13 +0,0 @@ -package com.tangem.features.onboarding.v2.wallet12.api - -import com.tangem.core.decompose.factory.ComponentFactory -import com.tangem.core.decompose.navigation.inner.InnerNavigationHolder -import com.tangem.core.ui.decompose.ComposableContentComponent -import com.tangem.domain.models.scan.ScanResponse - -interface OnboardingWallet12Component : ComposableContentComponent, InnerNavigationHolder { - - data class Params(val scanResponse: ScanResponse) - - interface Factory : ComponentFactory -} \ No newline at end of file diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/wallet12/impl/DefaultOnboardingWallet12Component.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/wallet12/impl/DefaultOnboardingWallet12Component.kt deleted file mode 100644 index edfbb70eed..0000000000 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/wallet12/impl/DefaultOnboardingWallet12Component.kt +++ /dev/null @@ -1,46 +0,0 @@ -package com.tangem.features.onboarding.v2.wallet12.impl - -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import com.tangem.core.decompose.context.AppComponentContext -import com.tangem.core.decompose.navigation.inner.InnerNavigation -import com.tangem.core.decompose.navigation.inner.InnerNavigationState -import com.tangem.features.onboarding.v2.wallet12.api.OnboardingWallet12Component -import dagger.assisted.Assisted -import dagger.assisted.AssistedFactory -import dagger.assisted.AssistedInject -import kotlinx.coroutines.flow.MutableStateFlow - -@Suppress("UnusedPrivateMember") -class DefaultOnboardingWallet12Component @AssistedInject constructor( - @Assisted private val context: AppComponentContext, - @Assisted private val params: OnboardingWallet12Component.Params, -) : OnboardingWallet12Component, AppComponentContext by context { - - override val innerNavigation: InnerNavigation = object : InnerNavigation { - override val state = MutableStateFlow( - Wallet12InnerNavigationState(0, null), // TODO - ) - - override fun pop(onComplete: (Boolean) -> Unit) { - // TODO - } - } - - @Composable - override fun Content(modifier: Modifier) { - } - - @AssistedFactory - interface Factory : OnboardingWallet12Component.Factory { - override fun create( - context: AppComponentContext, - params: OnboardingWallet12Component.Params, - ): DefaultOnboardingWallet12Component - } -} - -data class Wallet12InnerNavigationState( - override val stackSize: Int, - override val stackMaxSize: Int?, -) : InnerNavigationState \ No newline at end of file diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/wallet12/impl/di/ComponentModule.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/wallet12/impl/di/ComponentModule.kt deleted file mode 100644 index 70322dc3b1..0000000000 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/wallet12/impl/di/ComponentModule.kt +++ /dev/null @@ -1,20 +0,0 @@ -package com.tangem.features.onboarding.v2.wallet12.impl.di - -import com.tangem.features.onboarding.v2.wallet12.api.OnboardingWallet12Component -import com.tangem.features.onboarding.v2.wallet12.impl.DefaultOnboardingWallet12Component -import dagger.Binds -import dagger.Module -import dagger.hilt.InstallIn -import dagger.hilt.components.SingletonComponent -import javax.inject.Singleton - -@Module -@InstallIn(SingletonComponent::class) -internal interface ComponentModule { - - @Binds - @Singleton - fun bindOnboardingWallet12Component( - factory: DefaultOnboardingWallet12Component.Factory, - ): OnboardingWallet12Component.Factory -} \ No newline at end of file diff --git a/libs/tangem-sdk-api/.gitignore b/libs/tangem-sdk-api/.gitignore new file mode 100644 index 0000000000..42afabfd2a --- /dev/null +++ b/libs/tangem-sdk-api/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/libs/tangem-sdk-api/build.gradle.kts b/libs/tangem-sdk-api/build.gradle.kts new file mode 100644 index 0000000000..91756f3226 --- /dev/null +++ b/libs/tangem-sdk-api/build.gradle.kts @@ -0,0 +1,35 @@ +plugins { + alias(deps.plugins.android.library) + alias(deps.plugins.kotlin.android) + alias(deps.plugins.kotlin.kapt) + alias(deps.plugins.kotlin.serialization) + alias(deps.plugins.hilt.android) + id("configuration") +} + +android { + namespace = "com.tangem.legacy" +} + +dependencies { + implementation(projects.common) + implementation(projects.domain.models) + implementation(projects.domain.card) + implementation(projects.domain.legacy) + implementation(projects.domain.wallets.models) + + implementation(projects.core.res) + + /** Tangem libraries */ + implementation(deps.tangem.card.core) + implementation(deps.tangem.card.android) { + exclude(module = "joda-time") + } + + /** Other libraries */ + implementation(deps.timber) + + /** DI */ + implementation(deps.hilt.android) + kapt(deps.hilt.kapt) +} \ No newline at end of file diff --git a/libs/tangem-sdk-api/src/main/kotlin/com/tangem/sdk/api/CreateProductWalletTaskResponse.kt b/libs/tangem-sdk-api/src/main/kotlin/com/tangem/sdk/api/CreateProductWalletTaskResponse.kt new file mode 100644 index 0000000000..e57c41c193 --- /dev/null +++ b/libs/tangem-sdk-api/src/main/kotlin/com/tangem/sdk/api/CreateProductWalletTaskResponse.kt @@ -0,0 +1,24 @@ +package com.tangem.sdk.api + +import com.tangem.common.card.Card +import com.tangem.domain.models.scan.CardDTO +import com.tangem.domain.models.scan.KeyWalletPublicKey +import com.tangem.operations.CommandResponse +import com.tangem.operations.backup.PrimaryCard +import com.tangem.operations.derivation.ExtendedPublicKeysMap + +data class CreateProductWalletTaskResponse( + val card: CardDTO, + val derivedKeys: Map = mapOf(), + val primaryCard: PrimaryCard? = null, +) : CommandResponse { + constructor( + card: Card, + derivedKeys: Map = mapOf(), + primaryCard: PrimaryCard? = null, + ) : this( + card = CardDTO(card), + derivedKeys = derivedKeys, + primaryCard = primaryCard, + ) +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/sdk/TangemSdkManager.kt b/libs/tangem-sdk-api/src/main/kotlin/com/tangem/sdk/api/TangemSdkManager.kt similarity index 93% rename from app/src/main/java/com/tangem/tap/domain/sdk/TangemSdkManager.kt rename to libs/tangem-sdk-api/src/main/kotlin/com/tangem/sdk/api/TangemSdkManager.kt index 49bef8a287..a2c6cc8c4f 100644 --- a/app/src/main/java/com/tangem/tap/domain/sdk/TangemSdkManager.kt +++ b/libs/tangem-sdk-api/src/main/kotlin/com/tangem/sdk/api/TangemSdkManager.kt @@ -1,4 +1,4 @@ -package com.tangem.tap.domain.sdk +package com.tangem.sdk.api import androidx.annotation.DrawableRes import androidx.annotation.StringRes @@ -19,7 +19,6 @@ import com.tangem.domain.wallets.models.UserWalletId import com.tangem.operations.derivation.DerivationTaskResponse import com.tangem.operations.preflightread.PreflightReadFilter import com.tangem.operations.wallet.CreateWalletResponse -import com.tangem.tap.domain.tasks.product.CreateProductWalletTaskResponse @Suppress("TooManyFunctions") interface TangemSdkManager { @@ -96,8 +95,8 @@ interface TangemSdkManager { ): CompletionResult @Deprecated( - "TangemSdkManager shouldn't run custom tasks. " + - "All of them should be specified in TangemSdkManager certain methods.", + "com.tangem.sdk.api.TangemSdkManager shouldn't run custom tasks. " + + "All of them should be specified in com.tangem.sdk.api.TangemSdkManager certain methods.", ) suspend fun runTaskAsync( runnable: CardSessionRunnable, @@ -111,7 +110,7 @@ interface TangemSdkManager { @Suppress("MagicNumber") fun changeDisplayedCardIdNumbersCount(scanResponse: ScanResponse?) - @Deprecated("TangemSdkManager shouldn't returns a string from resources") + @Deprecated("com.tangem.sdk.api.TangemSdkManager shouldn't returns a string from resources") fun getString(@StringRes stringResId: Int, vararg formatArgs: Any?): String fun setUserCodeRequestPolicy(policy: UserCodeRequestPolicy) diff --git a/libs/tangem-sdk-api/src/main/kotlin/com/tangem/sdk/api/TapErrors.kt b/libs/tangem-sdk-api/src/main/kotlin/com/tangem/sdk/api/TapErrors.kt new file mode 100644 index 0000000000..b4fefc6ca6 --- /dev/null +++ b/libs/tangem-sdk-api/src/main/kotlin/com/tangem/sdk/api/TapErrors.kt @@ -0,0 +1,49 @@ +package com.tangem.sdk.api + +import androidx.annotation.StringRes +import com.tangem.common.core.TangemError +import com.tangem.legacy.R + +interface TapErrors + +interface ArgError { + val args: List? +} + +interface MultiMessageError : TapErrors { + val errorList: List + val builder: (List) -> String +} + +sealed class TapError( + @StringRes val messageResource: Int, + override val args: List? = null, +) : Throwable(), TapErrors, ArgError { + + object UnknownError : TapError(R.string.send_error_unknown) + open class CustomError(val customMessage: String) : TapError(R.string.common_custom_string, listOf(customMessage)) + + object NoInternetConnection : TapError(R.string.wallet_notification_no_internet) + + sealed class WalletManager { + class NoAccountError(amountToCreateAccount: String) : CustomError(amountToCreateAccount) + class InternalError(message: String) : CustomError(message) + object BlockchainIsUnreachableTryLater : TapError(R.string.wallet_balance_blockchain_unreachable_try_later) + } +} + +sealed class TapSdkError(override val messageResId: Int?) : TangemError(code = 50100) { + override var customMessage: String = code.toString() + + object CardForDifferentApp : TapSdkError(R.string.alert_unsupported_card) + object CardNotSupportedByRelease : TapSdkError(R.string.error_wrong_card_type) +} + +fun TapErrors.assembleErrors(): MutableList?>> { + val idList = mutableListOf?>>() + when (this) { + is MultiMessageError -> this.errorList.forEach { idList.addAll(it.assembleErrors()) } + is TapError -> idList.add(Pair(this.messageResource, this.args)) + } + return idList +} \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index 251a4e76a0..f915fb1945 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -147,6 +147,7 @@ include(":libs:auth") include(":libs:blockchain-sdk") include(":libs:crypto") include(":libs:visa") +include(":libs:tangem-sdk-api") // endregion Libs modules // region Feature modules From a55e775ed4ec479c9b57ff8a935908d64cae3b47 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 1 Nov 2024 15:29:38 +0500 Subject: [PATCH 070/106] Updated on 2026-08-14 --- app/src/main/assets/tangem-app-config | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/assets/tangem-app-config b/app/src/main/assets/tangem-app-config index cf01c91626..544358e464 160000 --- a/app/src/main/assets/tangem-app-config +++ b/app/src/main/assets/tangem-app-config @@ -1 +1 @@ -Subproject commit cf01c91626bf58bdf6895c59a706b83c37a9e345 +Subproject commit 544358e4640ae25402dd4186e0f244f9509c8906 From b8d1c54957a7b8dd38c55324f387ce33e4b2dc9e Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 1 Nov 2024 13:51:51 +0300 Subject: [PATCH 071/106] Updated on 2026-08-14 --- .../mercuryo/MercuryoBlockchainMapping.kt | 145 ++++++++++++++++++ .../mercuryo/MercuryoService.kt | 35 +---- .../exchangeServices/moonpay/BlockchainExt.kt | 37 ----- .../moonpay/MoonpayBlockchainMapping.kt | 145 ++++++++++++++++++ 4 files changed, 292 insertions(+), 70 deletions(-) create mode 100644 app/src/main/java/com/tangem/tap/network/exchangeServices/mercuryo/MercuryoBlockchainMapping.kt delete mode 100644 app/src/main/java/com/tangem/tap/network/exchangeServices/moonpay/BlockchainExt.kt create mode 100644 app/src/main/java/com/tangem/tap/network/exchangeServices/moonpay/MoonpayBlockchainMapping.kt diff --git a/app/src/main/java/com/tangem/tap/network/exchangeServices/mercuryo/MercuryoBlockchainMapping.kt b/app/src/main/java/com/tangem/tap/network/exchangeServices/mercuryo/MercuryoBlockchainMapping.kt new file mode 100644 index 0000000000..946d2afce1 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/network/exchangeServices/mercuryo/MercuryoBlockchainMapping.kt @@ -0,0 +1,145 @@ +package com.tangem.tap.network.exchangeServices.mercuryo + +import com.tangem.blockchain.common.Blockchain + +/** + * Map [Blockchain] to ids from [link](https://api.mercuryo.io/v1.6/lib/currencies) [crypto_currencies] + */ +@Suppress("CyclomaticComplexMethod") +internal val Blockchain.mercuryoNetwork: String? + get() { + return when (this) { + Blockchain.Algorand -> "ALGORAND" + Blockchain.Arbitrum -> "ARBITRUM" + Blockchain.Avalanche -> "AVALANCHE" + Blockchain.BSC -> "BINANCESMARTCHAIN" + Blockchain.Bitcoin -> "BITCOIN" + Blockchain.BitcoinCash -> "BITCOINCASH" + Blockchain.Cardano -> "CARDANO" + Blockchain.Cosmos -> "COSMOS" + Blockchain.Dash -> "DASH" + Blockchain.Dogecoin -> "DOGECOIN" + Blockchain.Ethereum -> "ETHEREUM" + Blockchain.Fantom -> "FANTOM" + Blockchain.Kusama -> "KUSAMA" + Blockchain.Litecoin -> "LITECOIN" + Blockchain.Near -> "NEAR_PROTOCOL" + Blockchain.TON -> "NEWTON" + Blockchain.Optimism -> "OPTIMISM" + Blockchain.Polkadot -> "POLKADOT" + Blockchain.Polygon -> "POLYGON" + Blockchain.XRP -> "RIPPLE" + Blockchain.Solana -> "SOLANA" + Blockchain.Stellar -> "STELLAR" + Blockchain.Tezos -> "TEZOS" + Blockchain.Tron -> "TRON" + Blockchain.ZkSyncEra -> "ZKSYNC" + Blockchain.Base -> "BASE" + Blockchain.ArbitrumTestnet -> null + Blockchain.AvalancheTestnet -> null + Blockchain.Binance -> null + Blockchain.BinanceTestnet -> null + Blockchain.BSCTestnet -> null + Blockchain.BitcoinTestnet -> null + Blockchain.BitcoinCashTestnet -> null + Blockchain.CosmosTestnet -> null + Blockchain.Ducatus -> null + Blockchain.EthereumTestnet -> null + Blockchain.EthereumClassic -> null + Blockchain.EthereumClassicTestnet -> null + Blockchain.FantomTestnet -> null + Blockchain.NearTestnet -> null + Blockchain.PolkadotTestnet -> null + Blockchain.Kava -> null + Blockchain.KavaTestnet -> null + Blockchain.PolygonTestnet -> null + Blockchain.RSK -> null + Blockchain.Sei -> null + Blockchain.SeiTestnet -> null + Blockchain.StellarTestnet -> null + Blockchain.SolanaTestnet -> null + Blockchain.TronTestnet -> null + Blockchain.Gnosis -> null + Blockchain.OptimismTestnet -> null + Blockchain.Dischain -> null + Blockchain.EthereumPow -> null + Blockchain.EthereumPowTestnet -> null + Blockchain.Kaspa -> null + Blockchain.Telos -> null + Blockchain.TelosTestnet -> null + Blockchain.TONTestnet -> null + Blockchain.Ravencoin -> null + Blockchain.RavencoinTestnet -> null + Blockchain.TerraV1 -> null + Blockchain.TerraV2 -> null + Blockchain.Cronos -> null + Blockchain.AlephZero -> null + Blockchain.AlephZeroTestnet -> null + Blockchain.OctaSpace -> null + Blockchain.OctaSpaceTestnet -> null + Blockchain.Chia -> null + Blockchain.ChiaTestnet -> null + Blockchain.Decimal -> null + Blockchain.DecimalTestnet -> null + Blockchain.XDC -> null + Blockchain.XDCTestnet -> null + Blockchain.VeChain -> null + Blockchain.VeChainTestnet -> null + Blockchain.Aptos -> null + Blockchain.AptosTestnet -> null + Blockchain.Playa3ull -> null + Blockchain.Shibarium -> null + Blockchain.ShibariumTestnet -> null + Blockchain.AlgorandTestnet -> null + Blockchain.Hedera -> null + Blockchain.HederaTestnet -> null + Blockchain.Aurora -> null + Blockchain.AuroraTestnet -> null + Blockchain.Areon -> null + Blockchain.AreonTestnet -> null + Blockchain.PulseChain -> null + Blockchain.PulseChainTestnet -> null + Blockchain.ZkSyncEraTestnet -> null + Blockchain.Nexa -> null + Blockchain.NexaTestnet -> null + Blockchain.Moonbeam -> null + Blockchain.MoonbeamTestnet -> null + Blockchain.Manta -> null + Blockchain.MantaTestnet -> null + Blockchain.PolygonZkEVM -> null + Blockchain.PolygonZkEVMTestnet -> null + Blockchain.Radiant -> null + Blockchain.BaseTestnet -> null + Blockchain.Moonriver -> null + Blockchain.MoonriverTestnet -> null + Blockchain.Mantle -> null + Blockchain.MantleTestnet -> null + Blockchain.Flare -> null + Blockchain.FlareTestnet -> null + Blockchain.Taraxa -> null + Blockchain.TaraxaTestnet -> null + Blockchain.Koinos -> null + Blockchain.KoinosTestnet -> null + Blockchain.Joystream -> null + Blockchain.Bittensor -> null + Blockchain.Filecoin -> null + Blockchain.Blast -> null + Blockchain.BlastTestnet -> null + Blockchain.Cyber -> null + Blockchain.CyberTestnet -> null + Blockchain.InternetComputer -> null + Blockchain.Sui -> null + Blockchain.SuiTestnet -> null + Blockchain.EnergyWebChain -> null + Blockchain.EnergyWebChainTestnet -> null + Blockchain.EnergyWebX -> null + Blockchain.EnergyWebXTestnet -> null + Blockchain.Casper -> null + Blockchain.CasperTestnet -> null + Blockchain.Core -> null + Blockchain.CoreTestnet -> null + Blockchain.Unknown -> null + Blockchain.Xodex -> null + Blockchain.Canxium -> null + } + } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/network/exchangeServices/mercuryo/MercuryoService.kt b/app/src/main/java/com/tangem/tap/network/exchangeServices/mercuryo/MercuryoService.kt index f4ceb2e9d8..50b558907b 100644 --- a/app/src/main/java/com/tangem/tap/network/exchangeServices/mercuryo/MercuryoService.kt +++ b/app/src/main/java/com/tangem/tap/network/exchangeServices/mercuryo/MercuryoService.kt @@ -30,7 +30,7 @@ internal class MercuryoService(private val environment: MercuryoEnvironment) : E override fun availableForBuy(scanResponse: ScanResponse, currency: Currency): Boolean { if (!isBuyAllowed()) return false - val mercuryoNetwork = currency.blockchain.mercuryoNetwork() + val mercuryoNetwork = currency.blockchain.mercuryoNetwork val contractAddress = (currency as? Currency.Token)?.token?.contractAddress ?: "" val availableCurrency = availableMercuryoCurrencies.firstOrNull { it.currencySymbol == currency.currencySymbol && @@ -77,7 +77,7 @@ internal class MercuryoService(private val environment: MercuryoEnvironment) : E .appendQueryParameter("redirect_url", ExchangeUrlBuilder.SUCCESS_URL) if (isDarkTheme) builder.appendQueryParameter("theme", "1inch") - blockchain.mercuryoNetwork()?.let { + blockchain.mercuryoNetwork?.let { builder.appendQueryParameter("network", it) } @@ -91,37 +91,6 @@ internal class MercuryoService(private val environment: MercuryoEnvironment) : E availableMercuryoCurrencies.addAll(data.config.cryptoCurrencies) } - @Suppress("CyclomaticComplexMethod") - private fun Blockchain.mercuryoNetwork(): String? { - return when (this) { - // Blockchain.Algorand -> "ALGORAND" //TODO: Uncomment with algo support - Blockchain.Arbitrum -> "ARBITRUM" - Blockchain.Avalanche -> "AVALANCHE" - Blockchain.BSC -> "BINANCESMARTCHAIN" - Blockchain.Bitcoin -> "BITCOIN" - Blockchain.BitcoinCash -> "BITCOINCASH" - Blockchain.Cardano -> "CARDANO" - Blockchain.Cosmos -> "COSMOS" - Blockchain.Dash -> "DASH" - Blockchain.Dogecoin -> "DOGECOIN" - Blockchain.Ethereum -> "ETHEREUM" - Blockchain.Fantom -> "FANTOM" - Blockchain.Kusama -> "KUSAMA" - Blockchain.Litecoin -> "LITECOIN" - Blockchain.Near -> "NEAR_PROTOCOL" - Blockchain.TON -> "NEWTON" - Blockchain.Optimism -> "OPTIMISM" - Blockchain.Polkadot -> "POLKADOT" - Blockchain.Polygon -> "POLYGON" - Blockchain.XRP -> "RIPPLE" - Blockchain.Solana -> "SOLANA" - Blockchain.Stellar -> "STELLAR" - Blockchain.Tezos -> "TEZOS" - Blockchain.Tron -> "TRON" - else -> null - } - } - private fun signature(address: String) = (address + environment.secret).calculateSha512().toHexString().lowercase() private companion object { diff --git a/app/src/main/java/com/tangem/tap/network/exchangeServices/moonpay/BlockchainExt.kt b/app/src/main/java/com/tangem/tap/network/exchangeServices/moonpay/BlockchainExt.kt deleted file mode 100644 index c5f04b9039..0000000000 --- a/app/src/main/java/com/tangem/tap/network/exchangeServices/moonpay/BlockchainExt.kt +++ /dev/null @@ -1,37 +0,0 @@ -package com.tangem.tap.network.exchangeServices.moonpay - -import com.tangem.blockchain.common.Blockchain -import com.tangem.blockchain.common.Blockchain.* -import com.tangem.tap.network.exchangeServices.moonpay.models.MoonPaySupportedCurrency - -internal val Blockchain.moonPaySupportedCurrency: MoonPaySupportedCurrency? - get() = when (this) { - Algorand -> MoonPaySupportedCurrency(networkCode = "algorand", currencyCode = "algo") - Aptos -> MoonPaySupportedCurrency(networkCode = "aptos", currencyCode = "apt") - Arbitrum -> MoonPaySupportedCurrency(networkCode = "arbitrum", currencyCode = "eth_arbitrum") - Avalanche -> MoonPaySupportedCurrency(networkCode = "avalanche_c_chain", currencyCode = "avax_cchain") - Binance -> MoonPaySupportedCurrency(networkCode = "bnb_chain", currencyCode = "bnb") - Bitcoin -> MoonPaySupportedCurrency(networkCode = "bitcoin", currencyCode = "btc") - BitcoinCash -> MoonPaySupportedCurrency(networkCode = "bitcoin_cash", currencyCode = "bch") - BSC -> MoonPaySupportedCurrency(networkCode = "binance_smart_chain", currencyCode = "bnb_bsc") - Cardano -> MoonPaySupportedCurrency(networkCode = "cardano", currencyCode = "ada") - Cosmos -> MoonPaySupportedCurrency(networkCode = "cosmos", currencyCode = "atom") - Dogecoin -> MoonPaySupportedCurrency(networkCode = "dogecoin", currencyCode = "doge") - Ethereum -> MoonPaySupportedCurrency(networkCode = "ethereum", currencyCode = "eth") - EthereumClassic -> MoonPaySupportedCurrency(networkCode = "ethereum_classic", currencyCode = "etc") - Hedera -> MoonPaySupportedCurrency(networkCode = "hedera", currencyCode = "hbar") - Litecoin -> MoonPaySupportedCurrency(networkCode = "litecoin", currencyCode = "ltc") - Near -> MoonPaySupportedCurrency(networkCode = "near", currencyCode = "near") - Optimism -> MoonPaySupportedCurrency(networkCode = "optimism", currencyCode = "eth_optimism") - Polkadot -> MoonPaySupportedCurrency(networkCode = "polkadot", currencyCode = "dot") - Polygon -> MoonPaySupportedCurrency(networkCode = "polygon", currencyCode = "matic_polygon") - Ravencoin -> MoonPaySupportedCurrency(networkCode = "ravencoin", currencyCode = "rvn") - Solana -> MoonPaySupportedCurrency(networkCode = "solana", currencyCode = "sol") - Stellar -> MoonPaySupportedCurrency(networkCode = "stellar", currencyCode = "xlm") - Tezos -> MoonPaySupportedCurrency(networkCode = "tezos", currencyCode = "xtz") - TON -> MoonPaySupportedCurrency(networkCode = "ton", currencyCode = "ton") - Tron -> MoonPaySupportedCurrency(networkCode = "tron", currencyCode = "trx") - VeChain -> MoonPaySupportedCurrency(networkCode = "vechain", currencyCode = "vet") - XRP -> MoonPaySupportedCurrency(networkCode = "ripple", currencyCode = "xrp") - else -> null - } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/network/exchangeServices/moonpay/MoonpayBlockchainMapping.kt b/app/src/main/java/com/tangem/tap/network/exchangeServices/moonpay/MoonpayBlockchainMapping.kt new file mode 100644 index 0000000000..fb01fff927 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/network/exchangeServices/moonpay/MoonpayBlockchainMapping.kt @@ -0,0 +1,145 @@ +package com.tangem.tap.network.exchangeServices.moonpay + +import com.tangem.blockchain.common.Blockchain +import com.tangem.blockchain.common.Blockchain.* +import com.tangem.tap.network.exchangeServices.moonpay.models.MoonPaySupportedCurrency + +/** + * Map [Blockchain] to [MoonPaySupportedCurrency.networkCode] and [MoonPaySupportedCurrency.currencyCode] + * from [link](https://api.moonpay.com/v3/currencies/) + */ +internal val Blockchain.moonPaySupportedCurrency: MoonPaySupportedCurrency? + get() = when (this) { + Algorand -> MoonPaySupportedCurrency(networkCode = "algorand", currencyCode = "algo") + Aptos -> MoonPaySupportedCurrency(networkCode = "aptos", currencyCode = "apt") + Arbitrum -> MoonPaySupportedCurrency(networkCode = "arbitrum", currencyCode = "eth_arbitrum") + Avalanche -> MoonPaySupportedCurrency(networkCode = "avalanche_c_chain", currencyCode = "avax_cchain") + Binance -> MoonPaySupportedCurrency(networkCode = "bnb_chain", currencyCode = "bnb") + Bitcoin -> MoonPaySupportedCurrency(networkCode = "bitcoin", currencyCode = "btc") + BitcoinCash -> MoonPaySupportedCurrency(networkCode = "bitcoin_cash", currencyCode = "bch") + BSC -> MoonPaySupportedCurrency(networkCode = "binance_smart_chain", currencyCode = "bnb_bsc") + Cardano -> MoonPaySupportedCurrency(networkCode = "cardano", currencyCode = "ada") + Cosmos -> MoonPaySupportedCurrency(networkCode = "cosmos", currencyCode = "atom") + Dogecoin -> MoonPaySupportedCurrency(networkCode = "dogecoin", currencyCode = "doge") + Ethereum -> MoonPaySupportedCurrency(networkCode = "ethereum", currencyCode = "eth") + EthereumClassic -> MoonPaySupportedCurrency(networkCode = "ethereum_classic", currencyCode = "etc") + Hedera -> MoonPaySupportedCurrency(networkCode = "hedera", currencyCode = "hbar") + Litecoin -> MoonPaySupportedCurrency(networkCode = "litecoin", currencyCode = "ltc") + Near -> MoonPaySupportedCurrency(networkCode = "near", currencyCode = "near") + Optimism -> MoonPaySupportedCurrency(networkCode = "optimism", currencyCode = "eth_optimism") + Polkadot -> MoonPaySupportedCurrency(networkCode = "polkadot", currencyCode = "dot") + Polygon -> MoonPaySupportedCurrency(networkCode = "polygon", currencyCode = "matic_polygon") + Ravencoin -> MoonPaySupportedCurrency(networkCode = "ravencoin", currencyCode = "rvn") + Solana -> MoonPaySupportedCurrency(networkCode = "solana", currencyCode = "sol") + Stellar -> MoonPaySupportedCurrency(networkCode = "stellar", currencyCode = "xlm") + Tezos -> MoonPaySupportedCurrency(networkCode = "tezos", currencyCode = "xtz") + TON -> MoonPaySupportedCurrency(networkCode = "ton", currencyCode = "ton") + Tron -> MoonPaySupportedCurrency(networkCode = "tron", currencyCode = "trx") + VeChain -> MoonPaySupportedCurrency(networkCode = "vechain", currencyCode = "vet") + XRP -> MoonPaySupportedCurrency(networkCode = "ripple", currencyCode = "xrp") + Sei -> MoonPaySupportedCurrency(networkCode = "sei", currencyCode = "sei_sei") + ZkSyncEra -> MoonPaySupportedCurrency(networkCode = "zksync", currencyCode = "eth_zksync") + Base -> MoonPaySupportedCurrency(networkCode = "base", currencyCode = "eth_base") + Filecoin -> MoonPaySupportedCurrency(networkCode = "filecoin", currencyCode = "fil") + Sui -> MoonPaySupportedCurrency(networkCode = "sui", currencyCode = "sui") + Core -> MoonPaySupportedCurrency(networkCode = "core", currencyCode = "core") + ArbitrumTestnet -> null + AvalancheTestnet -> null + BinanceTestnet -> null + BSCTestnet -> null + BitcoinTestnet -> null + BitcoinCashTestnet -> null + CosmosTestnet -> null + Ducatus -> null + EthereumTestnet -> null + EthereumClassicTestnet -> null + Fantom -> null + FantomTestnet -> null + NearTestnet -> null + PolkadotTestnet -> null + Kava -> null // doesn't support KavaEvm + KavaTestnet -> null + Kusama -> null + PolygonTestnet -> null + RSK -> null + SeiTestnet -> null + StellarTestnet -> null + SolanaTestnet -> null + TronTestnet -> null + Gnosis -> null + Dash -> null + OptimismTestnet -> null + Dischain -> null + EthereumPow -> null + EthereumPowTestnet -> null + Kaspa -> null + Telos -> null + TelosTestnet -> null + TONTestnet -> null + RavencoinTestnet -> null + TerraV1 -> null + TerraV2 -> null + Cronos -> null + AlephZero -> null + AlephZeroTestnet -> null + OctaSpace -> null + OctaSpaceTestnet -> null + Chia -> null + ChiaTestnet -> null + Decimal -> null + DecimalTestnet -> null + XDC -> null + XDCTestnet -> null + VeChainTestnet -> null + AptosTestnet -> null + Playa3ull -> null + Shibarium -> null + ShibariumTestnet -> null + AlgorandTestnet -> null + HederaTestnet -> null + Aurora -> null + AuroraTestnet -> null + Areon -> null + AreonTestnet -> null + PulseChain -> null + PulseChainTestnet -> null + ZkSyncEraTestnet -> null + Nexa -> null + NexaTestnet -> null + Moonbeam -> null + MoonbeamTestnet -> null + Manta -> null + MantaTestnet -> null + PolygonZkEVM -> null + PolygonZkEVMTestnet -> null + Radiant -> null + BaseTestnet -> null + Moonriver -> null + MoonriverTestnet -> null + Mantle -> null + MantleTestnet -> null + Flare -> null + FlareTestnet -> null + Taraxa -> null + TaraxaTestnet -> null + Koinos -> null + KoinosTestnet -> null + Joystream -> null + Bittensor -> null + Blast -> null + BlastTestnet -> null + Cyber -> null + CyberTestnet -> null + InternetComputer -> null + SuiTestnet -> null + EnergyWebChain -> null + EnergyWebChainTestnet -> null + EnergyWebX -> null + EnergyWebXTestnet -> null + Casper -> null + CasperTestnet -> null + CoreTestnet -> null + Unknown -> null + Xodex -> null + Canxium -> null + } \ No newline at end of file From 8bcf8b555b84002ec9b654dae39adaa2f7a46bd2 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 1 Nov 2024 15:26:06 +0400 Subject: [PATCH 072/106] Updated on 2026-08-14 --- .../ui/tokens/TokenItemStateConverter.kt | 71 +++++++++++++------ .../CryptoCurrencyToIconStateConverter.kt | 10 ++- .../token/internal/TokenFiatAmount.kt | 33 ++++++--- .../components/token/internal/TokenPrice.kt | 14 ++-- .../components/token/internal/TokenTitle.kt | 16 +++-- .../components/token/state/TokenItemState.kt | 10 ++- 6 files changed, 106 insertions(+), 48 deletions(-) diff --git a/common/ui/src/main/java/com/tangem/common/ui/tokens/TokenItemStateConverter.kt b/common/ui/src/main/java/com/tangem/common/ui/tokens/TokenItemStateConverter.kt index 913b07dd84..ae4993d6ce 100644 --- a/common/ui/src/main/java/com/tangem/common/ui/tokens/TokenItemStateConverter.kt +++ b/common/ui/src/main/java/com/tangem/common/ui/tokens/TokenItemStateConverter.kt @@ -1,5 +1,6 @@ package com.tangem.common.ui.tokens +import com.tangem.core.ui.components.currency.icon.CurrencyIconState import com.tangem.core.ui.components.currency.icon.converter.CryptoCurrencyToIconStateConverter import com.tangem.core.ui.components.marketprice.PriceChangeType import com.tangem.core.ui.components.marketprice.utils.PriceChangeConverter @@ -28,16 +29,20 @@ import java.math.BigDecimal */ class TokenItemStateConverter( private val appCurrency: AppCurrency, + private val iconStateProvider: (CryptoCurrencyStatus) -> CurrencyIconState = { + CryptoCurrencyToIconStateConverter().convert(it) + }, private val titleStateProvider: (CryptoCurrencyStatus) -> TokenItemState.TitleState = Companion::createTitleState, private val subtitleStateProvider: (CryptoCurrencyStatus) -> TokenItemState.SubtitleState? = { createSubtitleState(it, appCurrency) }, + private val fiatAmountStateProvider: (CryptoCurrencyStatus) -> TokenItemState.FiatAmountState? = { + createFiatAmountState(it, appCurrency) + }, private val onItemClick: (CryptoCurrencyStatus) -> Unit, private val onItemLongClick: ((CryptoCurrencyStatus) -> Unit)? = null, ) : Converter { - private val iconStateConverter by lazy(::CryptoCurrencyToIconStateConverter) - override fun convert(value: CryptoCurrencyStatus): TokenItemState { return when (value.value) { is CryptoCurrencyStatus.Loading -> value.mapToLoadingState() @@ -56,7 +61,7 @@ class TokenItemStateConverter( private fun CryptoCurrencyStatus.mapToLoadingState(): TokenItemState.Loading { return TokenItemState.Loading( id = currency.id.value, - iconState = iconStateConverter.convert(value = this), + iconState = iconStateProvider(this), titleState = titleStateProvider(this) as TokenItemState.TitleState.Content, subtitleState = requireNotNull(subtitleStateProvider(this)), ) @@ -65,13 +70,10 @@ class TokenItemStateConverter( private fun CryptoCurrencyStatus.mapToTokenItemState(): TokenItemState.Content { return TokenItemState.Content( id = currency.id.value, - iconState = iconStateConverter.convert(value = this), + iconState = iconStateProvider(this), titleState = titleStateProvider(this), subtitleState = requireNotNull(subtitleStateProvider(this)), - fiatAmountState = TokenItemState.FiatAmountState.Content( - text = getFormattedFiatAmount(), - hasStaked = !getStakedBalance().isZero(), - ), + fiatAmountState = requireNotNull(fiatAmountStateProvider(this)), subtitle2State = TokenItemState.Subtitle2State.TextContent(text = getFormattedAmount()), onItemClick = { onItemClick(this) }, onItemLongClick = onItemLongClick?.let { @@ -86,20 +88,10 @@ class TokenItemStateConverter( return amount.format { crypto(currency) } } - private fun CryptoCurrencyStatus.getFormattedFiatAmount(): String { - val fiatYieldBalance = value.fiatRate?.times(getStakedBalance()).orZero() - val fiatAmount = value.fiatAmount?.plus(fiatYieldBalance) ?: return DASH_SIGN - - return BigDecimalFormatter.formatFiatAmount(fiatAmount, appCurrency.code, appCurrency.symbol) - } - - private fun CryptoCurrencyStatus.getStakedBalance() = - (value.yieldBalance as? YieldBalance.Data)?.getTotalWithRewardsStakingBalance().orZero() - private fun CryptoCurrencyStatus.mapToUnreachableTokenItemState(): TokenItemState.Unreachable { return TokenItemState.Unreachable( id = currency.id.value, - iconState = iconStateConverter.convert(value = this), + iconState = iconStateProvider(this), titleState = titleStateProvider(this), subtitleState = subtitleStateProvider(this), onItemClick = { onItemClick(this) }, @@ -112,7 +104,7 @@ class TokenItemStateConverter( private fun CryptoCurrencyStatus.mapToNoAddressTokenItemState(): TokenItemState.NoAddress { return TokenItemState.NoAddress( id = currency.id.value, - iconState = iconStateConverter.convert(this), + iconState = iconStateProvider(this), titleState = titleStateProvider(this), subtitleState = subtitleStateProvider(this), onItemLongClick = onItemLongClick?.let { @@ -121,9 +113,9 @@ class TokenItemStateConverter( ) } - private companion object { + companion object { - fun createTitleState(currencyStatus: CryptoCurrencyStatus): TokenItemState.TitleState { + private fun createTitleState(currencyStatus: CryptoCurrencyStatus): TokenItemState.TitleState { return when (val value = currencyStatus.value) { is CryptoCurrencyStatus.Loading, is CryptoCurrencyStatus.MissedDerivation, @@ -145,7 +137,7 @@ class TokenItemStateConverter( } } - fun createSubtitleState( + private fun createSubtitleState( currencyStatus: CryptoCurrencyStatus, appCurrency: AppCurrency, ): TokenItemState.SubtitleState? { @@ -163,6 +155,29 @@ class TokenItemStateConverter( } } + private fun createFiatAmountState( + status: CryptoCurrencyStatus, + appCurrency: AppCurrency, + ): TokenItemState.FiatAmountState? { + return when (status.value) { + is CryptoCurrencyStatus.Loaded, + is CryptoCurrencyStatus.Custom, + is CryptoCurrencyStatus.NoQuote, + is CryptoCurrencyStatus.NoAccount, + -> { + TokenItemState.FiatAmountState.Content( + text = status.getFormattedFiatAmount(appCurrency), + hasStaked = !status.getStakedBalance().isZero(), + ) + } + is CryptoCurrencyStatus.Unreachable, + is CryptoCurrencyStatus.NoAmount, + is CryptoCurrencyStatus.MissedDerivation, + is CryptoCurrencyStatus.Loading, + -> null + } + } + private fun CryptoCurrencyStatus.getCryptoPriceState(appCurrency: AppCurrency): TokenItemState.SubtitleState { val fiatRate = value.fiatRate val priceChange = value.priceChange @@ -189,5 +204,15 @@ class TokenItemStateConverter( private fun BigDecimal.getPriceChangeType(): PriceChangeType { return PriceChangeConverter.fromBigDecimal(value = this) } + + fun CryptoCurrencyStatus.getFormattedFiatAmount(appCurrency: AppCurrency): String { + val fiatYieldBalance = value.fiatRate?.times(getStakedBalance()).orZero() + val fiatAmount = value.fiatAmount?.plus(fiatYieldBalance) ?: return DASH_SIGN + + return BigDecimalFormatter.formatFiatAmount(fiatAmount, appCurrency.code, appCurrency.symbol) + } + + private fun CryptoCurrencyStatus.getStakedBalance() = + (value.yieldBalance as? YieldBalance.Data)?.getTotalWithRewardsStakingBalance().orZero() } } \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/currency/icon/converter/CryptoCurrencyToIconStateConverter.kt b/core/ui/src/main/java/com/tangem/core/ui/components/currency/icon/converter/CryptoCurrencyToIconStateConverter.kt index c331fc8a1d..6c221c5b10 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/currency/icon/converter/CryptoCurrencyToIconStateConverter.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/currency/icon/converter/CryptoCurrencyToIconStateConverter.kt @@ -10,8 +10,12 @@ import com.tangem.utils.converter.Converter /** * Converts [CryptoCurrencyStatus] to [CurrencyIconState] + * + * @property isAvailable flag that indicates if the currency is available (affects on icon's grayscale) */ -class CryptoCurrencyToIconStateConverter : Converter { +class CryptoCurrencyToIconStateConverter( + private val isAvailable: Boolean = true, +) : Converter { override fun convert(value: CryptoCurrencyStatus): CurrencyIconState { return when (val currency = value.currency) { @@ -57,7 +61,7 @@ class CryptoCurrencyToIconStateConverter : Converter { - FiatAmountText( + ContentFiatAmount( text = state.text.orMaskWithStars(isBalanceHidden), hasStaked = state.hasStaked, modifier = modifier, ) } + is TokenItemState.FiatAmountState.TextContent -> { + FiatAmountText( + text = state.text.orMaskWithStars(isBalanceHidden), + modifier = modifier, + isAvailable = state.isAvailable, + ) + } is TokenFiatAmountState.Loading -> { RectangleShimmer(modifier = modifier.placeholderSize(), radius = TangemTheme.dimens.radius4) } @@ -41,7 +49,7 @@ internal fun TokenFiatAmount(state: TokenFiatAmountState?, isBalanceHidden: Bool } @Composable -private fun FiatAmountText(text: String, hasStaked: Boolean, modifier: Modifier = Modifier) { +private fun ContentFiatAmount(text: String, hasStaked: Boolean, modifier: Modifier = Modifier) { Row( modifier = modifier, verticalAlignment = Alignment.CenterVertically, @@ -58,16 +66,23 @@ private fun FiatAmountText(text: String, hasStaked: Boolean, modifier: Modifier .size(TangemTheme.dimens.size12), ) } - Text( - text = text, - color = TangemTheme.colors.text.primary1, - maxLines = 1, - overflow = TextOverflow.Ellipsis, - style = TangemTheme.typography.body2, - ) + + FiatAmountText(text = text) } } +@Composable +private fun FiatAmountText(text: String, modifier: Modifier = Modifier, isAvailable: Boolean = true) { + Text( + text = text, + modifier = modifier, + color = if (isAvailable) TangemTheme.colors.text.primary1 else TangemTheme.colors.text.tertiary, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + style = TangemTheme.typography.body2, + ) +} + private fun Modifier.placeholderSize(): Modifier = composed { return@composed this .padding(vertical = TangemTheme.dimens.spacing4) diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/token/internal/TokenPrice.kt b/core/ui/src/main/java/com/tangem/core/ui/components/token/internal/TokenPrice.kt index aacba7c8b7..f2986b6e11 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/token/internal/TokenPrice.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/token/internal/TokenPrice.kt @@ -37,8 +37,12 @@ internal fun TokenPrice(state: TokenPriceState?, modifier: Modifier = Modifier) priceChangePercent = state.priceChangePercent, ) } - is TokenPriceState.TextContent -> PriceText(text = state.value, modifier = modifier) - is TokenPriceState.Unknown -> PriceText(text = DASH_SIGN, modifier = modifier) + is TokenPriceState.TextContent -> { + PriceText(text = state.value, modifier = modifier, isAvailable = state.isAvailable) + } + is TokenPriceState.Unknown -> { + PriceText(text = DASH_SIGN, modifier = modifier) + } is TokenPriceState.Loading -> { RectangleShimmer(modifier = modifier.placeholderSize(), radius = TangemTheme.dimens.radius4) } @@ -71,11 +75,11 @@ private fun PriceBlock( } @Composable -private fun PriceText(text: String, modifier: Modifier = Modifier) { +private fun PriceText(text: String, modifier: Modifier = Modifier, isAvailable: Boolean = true) { AnimatedContent(targetState = text, label = "Update the price text", modifier = modifier) { animatedText -> Text( text = animatedText, - color = TangemTheme.colors.text.tertiary, + color = if (isAvailable) TangemTheme.colors.text.primary1 else TangemTheme.colors.text.tertiary, maxLines = 1, overflow = TextOverflow.Ellipsis, style = TangemTheme.typography.caption2, @@ -154,7 +158,7 @@ private class TokenPriceChangeStateProvider : CollectionPreviewParameterProvider priceChangePercent = "2.5%", type = PriceChangeType.NEUTRAL, ), - TokenPriceState.TextContent(value = "Subtitle"), + TokenPriceState.TextContent(value = "Subtitle", isAvailable = true), TokenPriceState.Unknown, TokenPriceState.Loading, TokenPriceState.Locked, diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/token/internal/TokenTitle.kt b/core/ui/src/main/java/com/tangem/core/ui/components/token/internal/TokenTitle.kt index 706120f20e..9bf3412f33 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/token/internal/TokenTitle.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/token/internal/TokenTitle.kt @@ -22,7 +22,7 @@ import com.tangem.core.ui.components.token.state.TokenItemState.TitleState as To internal fun TokenTitle(state: TokenTitleState?, modifier: Modifier = Modifier) { when (state) { is TokenTitleState.Content -> { - ContentTitle(name = state.text, hasPending = state.hasPending, modifier = modifier) + ContentTitle(state = state, modifier = modifier) } is TokenTitleState.Loading -> { RectangleShimmer(modifier = modifier.placeholderSize(), radius = TangemTheme.dimens.radius4) @@ -35,7 +35,7 @@ internal fun TokenTitle(state: TokenTitleState?, modifier: Modifier = Modifier) } @Composable -private fun ContentTitle(name: String, hasPending: Boolean, modifier: Modifier = Modifier) { +private fun ContentTitle(state: TokenTitleState.Content, modifier: Modifier = Modifier) { Row( modifier = modifier, horizontalArrangement = Arrangement.spacedBy(space = TangemTheme.dimens.spacing6), @@ -45,21 +45,25 @@ private fun ContentTitle(name: String, hasPending: Boolean, modifier: Modifier = * If currency name has a long width, then it will completely displace the image. * So we need to use [weight] to avoid displacement. */ - CurrencyNameText(name = name, modifier = Modifier.weight(weight = 1f, fill = false)) + CurrencyNameText( + name = state.text, + isAvailable = state.isAvailable, + modifier = Modifier.weight(weight = 1f, fill = false), + ) PendingTransactionImage( - hasPending = hasPending, + hasPending = state.hasPending, modifier = Modifier.align(alignment = Alignment.CenterVertically), ) } } @Composable -private fun CurrencyNameText(name: String, modifier: Modifier = Modifier) { +private fun CurrencyNameText(name: String, isAvailable: Boolean, modifier: Modifier = Modifier) { Text( text = name, modifier = modifier, - color = TangemTheme.colors.text.primary1, + color = if (isAvailable) TangemTheme.colors.text.primary1 else TangemTheme.colors.text.tertiary, overflow = TextOverflow.Ellipsis, maxLines = 1, style = TangemTheme.typography.subtitle2, diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/token/state/TokenItemState.kt b/core/ui/src/main/java/com/tangem/core/ui/components/token/state/TokenItemState.kt index 528978ea61..c33e5c8caa 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/token/state/TokenItemState.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/token/state/TokenItemState.kt @@ -160,7 +160,11 @@ sealed class TokenItemState { @Immutable sealed class TitleState { - data class Content(val text: String, val hasPending: Boolean = false) : TitleState() + data class Content( + val text: String, + val hasPending: Boolean = false, + val isAvailable: Boolean = true, + ) : TitleState() data object Loading : TitleState() @@ -176,7 +180,7 @@ sealed class TokenItemState { val type: PriceChangeType, ) : SubtitleState() - data class TextContent(val value: String) : SubtitleState() + data class TextContent(val value: String, val isAvailable: Boolean = true) : SubtitleState() data object Unknown : SubtitleState() @@ -192,6 +196,8 @@ sealed class TokenItemState { val hasStaked: Boolean = false, ) : FiatAmountState() + data class TextContent(val text: String, val isAvailable: Boolean = true) : FiatAmountState() + data object Loading : FiatAmountState() data object Locked : FiatAmountState() From e326ade6791acdee03ad3bf32a195590562cd634 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 1 Nov 2024 18:34:25 +0500 Subject: [PATCH 073/106] Updated on 2026-08-14 --- .../tangem/data/managetokens/DefaultManageTokensRepository.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/manage-tokens/src/main/kotlin/com/tangem/data/managetokens/DefaultManageTokensRepository.kt b/data/manage-tokens/src/main/kotlin/com/tangem/data/managetokens/DefaultManageTokensRepository.kt index 52f7501447..deb08005ef 100644 --- a/data/manage-tokens/src/main/kotlin/com/tangem/data/managetokens/DefaultManageTokensRepository.kt +++ b/data/manage-tokens/src/main/kotlin/com/tangem/data/managetokens/DefaultManageTokensRepository.kt @@ -109,7 +109,7 @@ internal class DefaultManageTokensRepository( ), active = true, searchText = query, - offset = request.offset + request.limit, + offset = request.offset, limit = request.limit, ).getOrThrow() } From 4c0d5727fd7e6653005b1e1716f7a86c486d978d Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 1 Nov 2024 18:42:34 +0300 Subject: [PATCH 074/106] Updated on 2026-08-14 --- .../datasource/api/express/models/response/ExchangeProvider.kt | 3 ++- .../tangem/feature/swap/domain/models/domain/SwapPairLeast.kt | 3 ++- .../src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt | 3 ++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/express/models/response/ExchangeProvider.kt b/core/datasource/src/main/java/com/tangem/datasource/api/express/models/response/ExchangeProvider.kt index b4ffbd4506..453d1e99c0 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/express/models/response/ExchangeProvider.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/express/models/response/ExchangeProvider.kt @@ -1,6 +1,7 @@ package com.tangem.datasource.api.express.models.response import com.squareup.moshi.Json +import java.math.BigDecimal data class ExchangeProvider( @Json(name = "id") @@ -28,7 +29,7 @@ data class ExchangeProvider( val isRecommended: Boolean = false, @Json(name = "slippage") - val slippage: Int?, + val slippage: BigDecimal?, ) enum class ExchangeProviderType { diff --git a/features/swap/domain/models/src/main/java/com/tangem/feature/swap/domain/models/domain/SwapPairLeast.kt b/features/swap/domain/models/src/main/java/com/tangem/feature/swap/domain/models/domain/SwapPairLeast.kt index 4b6f22eb71..561858f9f1 100644 --- a/features/swap/domain/models/src/main/java/com/tangem/feature/swap/domain/models/domain/SwapPairLeast.kt +++ b/features/swap/domain/models/src/main/java/com/tangem/feature/swap/domain/models/domain/SwapPairLeast.kt @@ -1,6 +1,7 @@ package com.tangem.feature.swap.domain.models.domain import com.tangem.domain.tokens.model.CryptoCurrencyStatus +import java.math.BigDecimal /** * Domain layer representation of SwapPair data network model. @@ -39,7 +40,7 @@ data class SwapProvider( val termsOfUse: String?, val privacyPolicy: String?, val isRecommended: Boolean = false, - val slippage: Int?, + val slippage: BigDecimal?, ) enum class ExchangeProviderType(val providerName: String) { diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt index 8176ed64cf..c270a8d837 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt @@ -15,6 +15,7 @@ import com.tangem.core.ui.format.bigdecimal.crypto import com.tangem.core.ui.format.bigdecimal.format import com.tangem.core.ui.format.bigdecimal.uncapped import com.tangem.core.ui.utils.BigDecimalFormatter +import com.tangem.core.ui.utils.parseBigDecimal import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.tokens.model.CryptoCurrencyStatus @@ -1113,7 +1114,7 @@ internal class StateBuilder( add( resourceReference( R.string.swapping_alert_slippage_description, - wrappedList("$slippage$PERCENT"), + wrappedList("${slippage.parseBigDecimal(1)}$PERCENT"), ), ) } From 8202b6bf2a383a4649fd87bf07360e84cc4b9d23 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 1 Nov 2024 18:42:51 +0300 Subject: [PATCH 075/106] Updated on 2026-08-14 --- .../main/java/com/tangem/core/ui/extensions/BlockchainIcons.kt | 2 +- .../src/main/java/com/tangem/blockchainsdk/utils/Blockchain.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/core/ui/src/main/java/com/tangem/core/ui/extensions/BlockchainIcons.kt b/core/ui/src/main/java/com/tangem/core/ui/extensions/BlockchainIcons.kt index 7a95f9e98c..28de4a0d24 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/extensions/BlockchainIcons.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/extensions/BlockchainIcons.kt @@ -99,7 +99,7 @@ fun getActiveIconResByCoinId(coinId: String): Int { "ethereum-classic" -> R.drawable.img_eth_classic_22 "stellar" -> R.drawable.img_stellar_22 "cardano" -> R.drawable.img_cardano_22 - "matic-network" -> R.drawable.img_polygon_22 + "matic-network", "polygon-ecosystem-token" -> R.drawable.img_polygon_22 "avalanche-2" -> R.drawable.img_avalanche_22 "solana" -> R.drawable.img_solana_22 "fantom" -> R.drawable.img_fantom_22 diff --git a/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/utils/Blockchain.kt b/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/utils/Blockchain.kt index 8a4ac6fc89..41bc95a6f8 100644 --- a/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/utils/Blockchain.kt +++ b/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/utils/Blockchain.kt @@ -295,7 +295,7 @@ fun Blockchain.toCoinId(): String { Blockchain.EthereumClassic, Blockchain.EthereumClassicTestnet -> "ethereum-classic" Blockchain.Stellar, Blockchain.StellarTestnet -> "stellar" Blockchain.Cardano -> "cardano" - Blockchain.Polygon, Blockchain.PolygonTestnet -> "matic-network" + Blockchain.Polygon, Blockchain.PolygonTestnet -> "polygon-ecosystem-token" Blockchain.Arbitrum, Blockchain.ArbitrumTestnet -> "arbitrum-one" Blockchain.Avalanche, Blockchain.AvalancheTestnet -> "avalanche-2" Blockchain.Solana, Blockchain.SolanaTestnet -> "solana" From ab85772d10af84429491bc3fae93340b57d09375 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 1 Nov 2024 17:47:18 +0200 Subject: [PATCH 076/106] Updated on 2026-08-14 --- .../converters/AmountReduceByTransformer.kt | 7 +- .../converters/AmountReduceToTransformer.kt | 6 +- .../converters/AmountStateConverter.kt | 23 +++--- .../converters/MaxEnterAmountConverter.kt | 19 +++++ .../field/AmountFieldChangeTransformer.kt | 12 +-- ... => AmountFieldSetMaxAmountTransformer.kt} | 12 +-- .../amountScreen/models/AmountParameters.kt | 8 ++ .../ui/amountScreen/models/AmountState.kt | 8 +- .../ui/amountScreen/models/MaxEnterAmount.kt | 9 +++ .../preview/AmountStatePreviewData.kt | 4 +- .../amountScreen/ui/AmountFieldContainer.kt | 4 +- .../ui/amountScreen/utils/AmountUtils.kt | 11 +-- core/res/src/main/res/values-de/strings.xml | 3 + core/res/src/main/res/values-es/strings.xml | 1 + core/res/src/main/res/values-fr/strings.xml | 1 + core/res/src/main/res/values-ja/strings.xml | 3 + core/res/src/main/res/values-ru/strings.xml | 1 + .../src/main/res/values-uk-rUA/strings.xml | 1 + core/res/src/main/res/values/strings.xml | 3 + .../presentation/state/SendStateFactory.kt | 20 ++++- .../fields/SendAmountFieldChangeConverter.kt | 9 ++- .../SendAmountFieldMaxAmountConverter.kt | 9 ++- .../send/impl/presentation/ui/SendScreen.kt | 4 +- .../state/InnerYieldBalanceState.kt | 7 +- .../state/StakingStateController.kt | 2 + .../presentation/state/StakingStateRouter.kt | 8 +- .../impl/presentation/state/StakingUiState.kt | 2 + .../state/converters/BalanceItemConverter.kt | 7 +- .../RewardsValidatorStateConverter.kt | 9 ++- .../converters/YieldBalancesConverter.kt | 2 +- .../helpers/StakingFeeTransactionLoader.kt | 8 +- .../state/helpers/StakingTransactionSender.kt | 4 +- .../previewdata/InitialStakingStatePreview.kt | 7 +- .../state/stub/StakingClickIntentsStub.kt | 2 +- .../transformers/SetAmountDataTransformer.kt | 55 ++++++++++++++ .../SetButtonsStateTransformer.kt | 2 +- .../SetConfirmationStateEmptyTransformer.kt | 1 + .../SetConfirmationStateInitTransformer.kt | 12 ++- .../SetInitialDataStateTransformer.kt | 13 +++- .../amount/AmountChangeStateTransformer.kt | 21 ++++- .../amount/AmountMaxValueStateTransformer.kt | 21 ++++- .../AmountRequirementStateTransformer.kt | 19 +++-- .../state/utils/StakingPendingActionUtils.kt | 6 +- .../ui/StakingClaimRewardsValidatorContent.kt | 4 +- .../ui/StakingInitialInfoContent.kt | 4 +- .../viewmodel/StakingClickIntents.kt | 2 +- .../viewmodel/StakingViewModel.kt | 76 ++++++++++++++----- 47 files changed, 356 insertions(+), 116 deletions(-) create mode 100644 common/ui/src/main/java/com/tangem/common/ui/amountScreen/converters/MaxEnterAmountConverter.kt rename common/ui/src/main/java/com/tangem/common/ui/amountScreen/converters/field/{AmountFieldMaxAmountTransformer.kt => AmountFieldSetMaxAmountTransformer.kt} (83%) create mode 100644 common/ui/src/main/java/com/tangem/common/ui/amountScreen/models/AmountParameters.kt create mode 100644 common/ui/src/main/java/com/tangem/common/ui/amountScreen/models/MaxEnterAmount.kt create mode 100644 features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetAmountDataTransformer.kt diff --git a/common/ui/src/main/java/com/tangem/common/ui/amountScreen/converters/AmountReduceByTransformer.kt b/common/ui/src/main/java/com/tangem/common/ui/amountScreen/converters/AmountReduceByTransformer.kt index 9597b3d764..0b9f4c1a5d 100644 --- a/common/ui/src/main/java/com/tangem/common/ui/amountScreen/converters/AmountReduceByTransformer.kt +++ b/common/ui/src/main/java/com/tangem/common/ui/amountScreen/converters/AmountReduceByTransformer.kt @@ -24,6 +24,9 @@ class AmountReduceByTransformer( private val cryptoCurrencyStatus: CryptoCurrencyStatus, private val value: ReduceByData, ) : Transformer { + + private val maxEnterAmountConverter = MaxEnterAmountConverter() + override fun transform(prevState: AmountState): AmountState { if (prevState !is AmountState.Data) return prevState @@ -40,8 +43,10 @@ class AmountReduceByTransformer( decimals = fiatDecimals, ) + val maxEnterAmount = maxEnterAmountConverter.convert(cryptoCurrencyStatus) + val checkValue = if (amountTextField.isFiatValue) fiatValue else cryptoValue - val isExceedBalance = checkValue.checkExceedBalance(cryptoCurrencyStatus, amountTextField) + val isExceedBalance = checkValue.checkExceedBalance(maxEnterAmount, amountTextField) val isZero = if (amountTextField.isFiatValue) { decimalFiatValue.isNullOrZero() } else { diff --git a/common/ui/src/main/java/com/tangem/common/ui/amountScreen/converters/AmountReduceToTransformer.kt b/common/ui/src/main/java/com/tangem/common/ui/amountScreen/converters/AmountReduceToTransformer.kt index c81634b818..74f6e66852 100644 --- a/common/ui/src/main/java/com/tangem/common/ui/amountScreen/converters/AmountReduceToTransformer.kt +++ b/common/ui/src/main/java/com/tangem/common/ui/amountScreen/converters/AmountReduceToTransformer.kt @@ -24,6 +24,8 @@ class AmountReduceToTransformer( private val cryptoCurrencyStatus: CryptoCurrencyStatus, private val value: BigDecimal, ) : Transformer { + private val maxEnterAmountConverter = MaxEnterAmountConverter() + override fun transform(prevState: AmountState): AmountState { if (prevState !is AmountState.Data) return prevState @@ -38,8 +40,10 @@ class AmountReduceToTransformer( decimals = fiatDecimals, ) + val maxEnterAmount = maxEnterAmountConverter.convert(cryptoCurrencyStatus) + val checkValue = if (amountTextField.isFiatValue) fiatValue else cryptoValue - val isExceedBalance = checkValue.checkExceedBalance(cryptoCurrencyStatus, amountTextField) + val isExceedBalance = checkValue.checkExceedBalance(maxEnterAmount, amountTextField) val isZero = if (amountTextField.isFiatValue) decimalFiatValue.isNullOrZero() else value.isNullOrZero() return prevState.copy( isPrimaryButtonEnabled = !isExceedBalance && !isZero, diff --git a/common/ui/src/main/java/com/tangem/common/ui/amountScreen/converters/AmountStateConverter.kt b/common/ui/src/main/java/com/tangem/common/ui/amountScreen/converters/AmountStateConverter.kt index 3445c4859f..e2adb46d11 100644 --- a/common/ui/src/main/java/com/tangem/common/ui/amountScreen/converters/AmountStateConverter.kt +++ b/common/ui/src/main/java/com/tangem/common/ui/amountScreen/converters/AmountStateConverter.kt @@ -3,8 +3,10 @@ package com.tangem.common.ui.amountScreen.converters import com.tangem.common.ui.R import com.tangem.common.ui.amountScreen.AmountScreenClickIntents import com.tangem.common.ui.amountScreen.converters.field.AmountFieldConverter +import com.tangem.common.ui.amountScreen.models.AmountParameters import com.tangem.common.ui.amountScreen.models.AmountSegmentedButtonsConfig import com.tangem.common.ui.amountScreen.models.AmountState +import com.tangem.common.ui.amountScreen.models.MaxEnterAmount import com.tangem.core.ui.components.currency.icon.converter.CryptoCurrencyToIconStateConverter import com.tangem.core.ui.extensions.resourceReference import com.tangem.core.ui.extensions.stringReference @@ -14,7 +16,6 @@ import com.tangem.core.ui.format.bigdecimal.format import com.tangem.core.ui.utils.BigDecimalFormatter.formatFiatAmount import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.tokens.model.CryptoCurrencyStatus -import com.tangem.domain.wallets.models.UserWallet import com.tangem.utils.Provider import com.tangem.utils.converter.Converter import com.tangem.utils.isNullOrZero @@ -25,17 +26,17 @@ import kotlinx.collections.immutable.persistentListOf * * @property clickIntents amount screen clicks * @property appCurrencyProvider selected app currency provider - * @property userWalletProvider selected user wallet provider + * @property maxEnterAmountProvider max enter amount data provider * @property cryptoCurrencyStatusProvider current cryptocurrency status provider * @property iconStateConverter currency icon converter */ class AmountStateConverter( private val clickIntents: AmountScreenClickIntents, private val appCurrencyProvider: Provider, - private val userWalletProvider: Provider, private val cryptoCurrencyStatusProvider: Provider, + private val maxEnterAmountProvider: Provider, private val iconStateConverter: CryptoCurrencyToIconStateConverter, -) : Converter { +) : Converter { private val amountFieldConverter by lazy(LazyThreadSafetyMode.NONE) { AmountFieldConverter( @@ -45,19 +46,19 @@ class AmountStateConverter( ) } - override fun convert(value: String): AmountState { - val userWallet = userWalletProvider() + override fun convert(value: AmountParameters): AmountState { + val maxEnterAmount = maxEnterAmountProvider() val appCurrency = appCurrencyProvider() val status = cryptoCurrencyStatusProvider() - val fiat = formatFiatAmount(status.value.fiatAmount, appCurrency.code, appCurrency.symbol) - val crypto = status.value.amount.format { crypto(status.currency) } + val fiat = formatFiatAmount(maxEnterAmount.fiatAmount, appCurrency.code, appCurrency.symbol) + val crypto = maxEnterAmount.amount.format { crypto(status.currency) } val noFeeRate = status.value.fiatRate.isNullOrZero() return AmountState.Data( - walletName = userWallet.name, - walletBalance = resourceReference(R.string.common_crypto_fiat_format, wrappedList(crypto, fiat)), + title = value.title, + availableBalance = resourceReference(R.string.common_crypto_fiat_format, wrappedList(crypto, fiat)), tokenIconState = iconStateConverter.convert(status), - amountTextField = amountFieldConverter.convert(value), + amountTextField = amountFieldConverter.convert(value.value), isPrimaryButtonEnabled = false, appCurrencyCode = appCurrency.code, segmentedButtonConfig = persistentListOf( diff --git a/common/ui/src/main/java/com/tangem/common/ui/amountScreen/converters/MaxEnterAmountConverter.kt b/common/ui/src/main/java/com/tangem/common/ui/amountScreen/converters/MaxEnterAmountConverter.kt new file mode 100644 index 0000000000..1b23b64181 --- /dev/null +++ b/common/ui/src/main/java/com/tangem/common/ui/amountScreen/converters/MaxEnterAmountConverter.kt @@ -0,0 +1,19 @@ +package com.tangem.common.ui.amountScreen.converters + +import com.tangem.common.ui.amountScreen.models.MaxEnterAmount +import com.tangem.domain.tokens.model.CryptoCurrencyStatus +import com.tangem.utils.converter.Converter + +/** + * Converts [CryptoCurrencyStatus] to [MaxEnterAmount] + */ +class MaxEnterAmountConverter : Converter { + + override fun convert(value: CryptoCurrencyStatus): MaxEnterAmount { + return MaxEnterAmount( + amount = value.value.amount, + fiatAmount = value.value.fiatAmount, + fiatRate = value.value.fiatRate, + ) + } +} \ No newline at end of file diff --git a/common/ui/src/main/java/com/tangem/common/ui/amountScreen/converters/field/AmountFieldChangeTransformer.kt b/common/ui/src/main/java/com/tangem/common/ui/amountScreen/converters/field/AmountFieldChangeTransformer.kt index 8de99b1fc5..b51fa49ad2 100644 --- a/common/ui/src/main/java/com/tangem/common/ui/amountScreen/converters/field/AmountFieldChangeTransformer.kt +++ b/common/ui/src/main/java/com/tangem/common/ui/amountScreen/converters/field/AmountFieldChangeTransformer.kt @@ -5,6 +5,7 @@ import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.input.KeyboardType import com.tangem.common.ui.R import com.tangem.common.ui.amountScreen.models.AmountState +import com.tangem.common.ui.amountScreen.models.MaxEnterAmount import com.tangem.common.ui.amountScreen.utils.checkExceedBalance import com.tangem.common.ui.amountScreen.utils.getCryptoValue import com.tangem.common.ui.amountScreen.utils.getFiatValue @@ -12,7 +13,6 @@ import com.tangem.common.ui.amountScreen.utils.getKeyboardAction import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.extensions.resourceReference import com.tangem.core.ui.utils.parseToBigDecimal -import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.utils.isNullOrZero import com.tangem.utils.transformer.Transformer import java.math.BigDecimal @@ -20,11 +20,11 @@ import java.math.BigDecimal /** * Amount value change * - * @property cryptoCurrencyStatus current cryptocurrency status + * @property maxEnterAmount max amount to enter * @property value amount value */ class AmountFieldChangeTransformer( - private val cryptoCurrencyStatus: CryptoCurrencyStatus, + private val maxEnterAmount: MaxEnterAmount, private val value: String, ) : Transformer { @@ -39,19 +39,19 @@ class AmountFieldChangeTransformer( val trimmedValue = value.trim() val cryptoValue = trimmedValue.getCryptoValue( - fiatRate = cryptoCurrencyStatus.value.fiatRate, + fiatRate = maxEnterAmount.fiatRate, isFiatValue = amountTextField.isFiatValue, decimals = cryptoDecimals, ) val decimalCryptoValue = cryptoValue.parseToBigDecimal(cryptoDecimals) val (fiatValue, decimalFiatValue) = trimmedValue.getFiatValue( - fiatRate = cryptoCurrencyStatus.value.fiatRate, + fiatRate = maxEnterAmount.fiatRate, isFiatValue = amountTextField.isFiatValue, decimals = fiatDecimals, ) val checkValue = if (amountTextField.isFiatValue) fiatValue else cryptoValue - val isExceedBalance = checkValue.checkExceedBalance(cryptoCurrencyStatus, amountTextField) + val isExceedBalance = checkValue.checkExceedBalance(maxEnterAmount, amountTextField) val isZero = if (amountTextField.isFiatValue) { decimalFiatValue.isNullOrZero() } else { diff --git a/common/ui/src/main/java/com/tangem/common/ui/amountScreen/converters/field/AmountFieldMaxAmountTransformer.kt b/common/ui/src/main/java/com/tangem/common/ui/amountScreen/converters/field/AmountFieldSetMaxAmountTransformer.kt similarity index 83% rename from common/ui/src/main/java/com/tangem/common/ui/amountScreen/converters/field/AmountFieldMaxAmountTransformer.kt rename to common/ui/src/main/java/com/tangem/common/ui/amountScreen/converters/field/AmountFieldSetMaxAmountTransformer.kt index 05a8705750..16c67d20f8 100644 --- a/common/ui/src/main/java/com/tangem/common/ui/amountScreen/converters/field/AmountFieldMaxAmountTransformer.kt +++ b/common/ui/src/main/java/com/tangem/common/ui/amountScreen/converters/field/AmountFieldSetMaxAmountTransformer.kt @@ -4,8 +4,8 @@ import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.input.KeyboardType import com.tangem.common.ui.amountScreen.models.AmountState +import com.tangem.common.ui.amountScreen.models.MaxEnterAmount import com.tangem.core.ui.utils.parseBigDecimal -import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.utils.isNullOrZero import com.tangem.utils.transformer.Transformer import java.math.RoundingMode @@ -13,10 +13,10 @@ import java.math.RoundingMode /** * Selects maximum amount value * - * @property cryptoCurrencyStatus current cryptocurrency status + * @property maxAmount maximum enter amount */ -class AmountFieldMaxAmountTransformer( - private val cryptoCurrencyStatus: CryptoCurrencyStatus, +class AmountFieldSetMaxAmountTransformer( + private val maxAmount: MaxEnterAmount, ) : Transformer { override fun transform(prevState: AmountState): AmountState { @@ -26,8 +26,8 @@ class AmountFieldMaxAmountTransformer( val cryptoDecimals = amountTextField.cryptoAmount.decimals val fiatDecimals = amountTextField.fiatAmount.decimals - val decimalCryptoValue = cryptoCurrencyStatus.value.amount - val decimalFiatValue = cryptoCurrencyStatus.value.fiatAmount + val decimalCryptoValue = maxAmount.amount + val decimalFiatValue = maxAmount.fiatAmount if (decimalCryptoValue.isNullOrZero()) return prevState diff --git a/common/ui/src/main/java/com/tangem/common/ui/amountScreen/models/AmountParameters.kt b/common/ui/src/main/java/com/tangem/common/ui/amountScreen/models/AmountParameters.kt new file mode 100644 index 0000000000..e84bfb4330 --- /dev/null +++ b/common/ui/src/main/java/com/tangem/common/ui/amountScreen/models/AmountParameters.kt @@ -0,0 +1,8 @@ +package com.tangem.common.ui.amountScreen.models + +import com.tangem.core.ui.extensions.TextReference + +data class AmountParameters( + val title: TextReference, + val value: String, +) \ No newline at end of file diff --git a/common/ui/src/main/java/com/tangem/common/ui/amountScreen/models/AmountState.kt b/common/ui/src/main/java/com/tangem/common/ui/amountScreen/models/AmountState.kt index d23a2892bf..52758a02cc 100644 --- a/common/ui/src/main/java/com/tangem/common/ui/amountScreen/models/AmountState.kt +++ b/common/ui/src/main/java/com/tangem/common/ui/amountScreen/models/AmountState.kt @@ -13,8 +13,8 @@ sealed class AmountState { /** * @param isPrimaryButtonEnabled indicates if next state button enabled - * @param walletName user wallet name - * @param walletBalance user crypto currency balance in wallet + * @param title title + * @param availableBalance user crypto currency balance * @param tokenIconState crypto currency icon state * @param segmentedButtonConfig currency switcher config * @param selectedButton selected currency index @@ -24,8 +24,8 @@ sealed class AmountState { */ data class Data( override val isPrimaryButtonEnabled: Boolean, - val walletName: String, - val walletBalance: TextReference, + val title: TextReference, + val availableBalance: TextReference, val tokenIconState: CurrencyIconState, val segmentedButtonConfig: PersistentList, val selectedButton: Int, diff --git a/common/ui/src/main/java/com/tangem/common/ui/amountScreen/models/MaxEnterAmount.kt b/common/ui/src/main/java/com/tangem/common/ui/amountScreen/models/MaxEnterAmount.kt new file mode 100644 index 0000000000..e9a5f88238 --- /dev/null +++ b/common/ui/src/main/java/com/tangem/common/ui/amountScreen/models/MaxEnterAmount.kt @@ -0,0 +1,9 @@ +package com.tangem.common.ui.amountScreen.models + +import java.math.BigDecimal + +data class MaxEnterAmount( + val amount: BigDecimal? = null, + val fiatAmount: BigDecimal? = null, + val fiatRate: BigDecimal? = null, +) \ No newline at end of file diff --git a/common/ui/src/main/java/com/tangem/common/ui/amountScreen/preview/AmountStatePreviewData.kt b/common/ui/src/main/java/com/tangem/common/ui/amountScreen/preview/AmountStatePreviewData.kt index a622f752c9..63c4d0cd5d 100644 --- a/common/ui/src/main/java/com/tangem/common/ui/amountScreen/preview/AmountStatePreviewData.kt +++ b/common/ui/src/main/java/com/tangem/common/ui/amountScreen/preview/AmountStatePreviewData.kt @@ -17,8 +17,8 @@ object AmountStatePreviewData { val amountState = AmountState.Data( isPrimaryButtonEnabled = false, - walletName = "Family Wallet", - walletBalance = stringReference("2 130,88 USDT (2 129,92 \$)"), + title = stringReference("Family Wallet"), + availableBalance = stringReference("2 130,88 USDT (2 129,92 \$)"), tokenIconState = CurrencyIconState.Loading, segmentedButtonConfig = persistentListOf( AmountSegmentedButtonsConfig( diff --git a/common/ui/src/main/java/com/tangem/common/ui/amountScreen/ui/AmountFieldContainer.kt b/common/ui/src/main/java/com/tangem/common/ui/amountScreen/ui/AmountFieldContainer.kt index caee01e48c..8b9904cc42 100644 --- a/common/ui/src/main/java/com/tangem/common/ui/amountScreen/ui/AmountFieldContainer.kt +++ b/common/ui/src/main/java/com/tangem/common/ui/amountScreen/ui/AmountFieldContainer.kt @@ -34,14 +34,14 @@ internal fun LazyListScope.amountField( .background(TangemTheme.colors.background.action), ) { Text( - text = amountState.walletName, + text = amountState.title.resolveReference(), style = TangemTheme.typography.subtitle2, color = TangemTheme.colors.text.tertiary, modifier = Modifier .padding(top = TangemTheme.dimens.spacing14), ) - val balance = amountState.walletBalance.orMaskWithStars(isBalanceHidden).resolveReference() + val balance = amountState.availableBalance.orMaskWithStars(isBalanceHidden).resolveReference() AnimatedContent( targetState = balance, label = "Hide Balance Animation", diff --git a/common/ui/src/main/java/com/tangem/common/ui/amountScreen/utils/AmountUtils.kt b/common/ui/src/main/java/com/tangem/common/ui/amountScreen/utils/AmountUtils.kt index 26492fa913..3f20ba474c 100644 --- a/common/ui/src/main/java/com/tangem/common/ui/amountScreen/utils/AmountUtils.kt +++ b/common/ui/src/main/java/com/tangem/common/ui/amountScreen/utils/AmountUtils.kt @@ -2,9 +2,9 @@ package com.tangem.common.ui.amountScreen.utils import androidx.compose.ui.text.input.ImeAction import com.tangem.common.ui.amountScreen.models.AmountFieldModel +import com.tangem.common.ui.amountScreen.models.MaxEnterAmount import com.tangem.core.ui.utils.parseBigDecimal import com.tangem.core.ui.utils.parseToBigDecimal -import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.utils.isNullOrZero import java.math.BigDecimal import java.math.RoundingMode @@ -36,12 +36,9 @@ internal fun String.getFiatValue( } } -internal fun String.checkExceedBalance( - cryptoCurrencyStatus: CryptoCurrencyStatus, - amountTextField: AmountFieldModel, -): Boolean { - val currencyCryptoAmount = cryptoCurrencyStatus.value.amount ?: BigDecimal.ZERO - val currencyFiatAmount = cryptoCurrencyStatus.value.fiatAmount ?: BigDecimal.ZERO +internal fun String.checkExceedBalance(maxEnterAmount: MaxEnterAmount, amountTextField: AmountFieldModel): Boolean { + val currencyCryptoAmount = maxEnterAmount.amount ?: BigDecimal.ZERO + val currencyFiatAmount = maxEnterAmount.fiatAmount ?: BigDecimal.ZERO val fiatDecimal = parseToBigDecimal(amountTextField.fiatAmount.decimals) val cryptoDecimal = parseToBigDecimal(amountTextField.cryptoAmount.decimals) return if (amountTextField.isFiatValue) { diff --git a/core/res/src/main/res/values-de/strings.xml b/core/res/src/main/res/values-de/strings.xml index 7fdccc468b..4313234fb9 100644 --- a/core/res/src/main/res/values-de/strings.xml +++ b/core/res/src/main/res/values-de/strings.xml @@ -698,6 +698,7 @@ Name Die Anzahl der zu stakenden Krypros muss mindesten %s betragen Der Stakingbetrag wird aufgrund der Netzwerkregeln auf %1$s TRX aufgerundet. + Der Betrag der unstaked wird, wird aufgrund der Netzwerkregeln auf %1$s TRX gerundet. Nicht gestakte beanspruche Gebühr für das Staking-Konto Ein Staking-Konto ist ein spezielles Konto, auf dem eingesetzte SOL-Token gelagert werden. Es wird erstellt, wenn du deine Token an einen Validator delegierst, um an der Transaktionsvalidierung teilzunehmen und Belohnungen zu verdienen. Für die Erstellung des Staking-Kontos wird eine geringe Gebühr erhoben, die nach Abschluss des Stakings zurückgegeben wird. @@ -783,6 +784,7 @@ Lösen der Bindungen Gelocktes unlocken Entsperren + Die Anzahl der zu stakenden Krypros muss mindesten %s betragen Der Betrag übersteigt das eingesetzte Guthaben Unstaken Staking beenden @@ -810,6 +812,7 @@ Neuer Swap-Anbieter verfügbar! Der Betrag umfasst:\n- Gebühr des Dienstanbieters\n- Netzgebühr für die Rücksendung von %s von der Vermittlungsstelle an die Adresse des Nutzers. Der Betrag enthält die Gebühren des Dienstleisters. + Anbieter-Slippage kann bis zu %s ausfallen. Gebühren Alle dezentralen Börsen benötigen Genehmigungen, um zu verhindern, dass intelligente Verträge ohne Ihre Erlaubnis auf Ihre Geldbörse zugreifen. Smart Contracts können nicht auf Ihre Token zugreifen, wenn Sie nicht zustimmen. Indem Sie Ihre Token \"freischalten\", ermächtigen Sie den 1-Zoll-Smart-Contract, sie auszugeben. Die Miner des Netzwerks erhalten eine (von Ihnen bezahlte) Gasgebühr, um diese Aktion in der Blockchain aufzuzeichnen. Sie können Ihre Token tauschen, nachdem Sie Ihre Zustimmung gegeben haben. Genehmigen diff --git a/core/res/src/main/res/values-es/strings.xml b/core/res/src/main/res/values-es/strings.xml index 03a98284df..a8fc4365c8 100644 --- a/core/res/src/main/res/values-es/strings.xml +++ b/core/res/src/main/res/values-es/strings.xml @@ -781,6 +781,7 @@ Desunión Desbloquear Desbloqueando + El monto del staking debe ser al menos %s El monto excede el saldo apostado Sin staking Unstaking diff --git a/core/res/src/main/res/values-fr/strings.xml b/core/res/src/main/res/values-fr/strings.xml index 7882e38152..42eb2abac8 100644 --- a/core/res/src/main/res/values-fr/strings.xml +++ b/core/res/src/main/res/values-fr/strings.xml @@ -781,6 +781,7 @@ Dissociation Débloquer Déverrouillage + Le montant à staker doit être au moins %s Le montant dépasse le solde misé Non-staké Unstaking diff --git a/core/res/src/main/res/values-ja/strings.xml b/core/res/src/main/res/values-ja/strings.xml index de3d33b7d2..8cdaeffe4a 100644 --- a/core/res/src/main/res/values-ja/strings.xml +++ b/core/res/src/main/res/values-ja/strings.xml @@ -688,6 +688,7 @@ 名前 ステーキング金額は %s 以上である必要があります ネットワークルールにより、ステーキング金額は%1$s TRX に切り上げられます。 + ネットワークルールにより、ステーキング解除の量は%1$s TRX に切り上げられます。 ステーキング解除分を請求する ステーキングアカウント手数料 ステーキングアカウントは、ステーキングされたSOLトークンが保管される特別なアカウントです。取引の検証に参加して報酬を得るために、トークンをバリデーターに委任すると、このアカウントが作成されます。ステーキングアカウントの作成には少額の手数料がかかりますが、この手数料はステーキングが完了すると返金されます。 @@ -773,6 +774,7 @@ ステーキング解約中 ロック解除 ロック解除中 + ステーキング金額は %s 以上である必要があります 金額がステーキング残高を超えています ステーキングされていない ステーキング解除 @@ -800,6 +802,7 @@ 新しいスワッププロバイダーが利用可能になりました! この金額には以下が含まれます:\n- サービスプロバイダーの手数料\n- 取引所からユーザーのアドレスに%s を送り返すためのネットワーク手数料。 この金額には、サービスプロバイダーの手数料が含まれています。 + プロバイダーのスリッページは最大%sです。 手数料 すべての分散型取引所は、スマートコントラクトがあなたの許可なくウォレットにアクセスするのを防ぐために承認を必要とします。設計上、スマートコントラクトは承認なしでトークンにアクセスできません。トークンを「ロック解除」することで、あなたは1-inchのスマートコントラクトがトークンを使うことを承認します。ネットワークのマイナーは、このアクションをブロックチェーンに記録するためのガス料金(あなたが支払う)を受け取ります。承認後、トークンを交換することができます。 承認 diff --git a/core/res/src/main/res/values-ru/strings.xml b/core/res/src/main/res/values-ru/strings.xml index ba6d9c4db6..c600ccc00e 100644 --- a/core/res/src/main/res/values-ru/strings.xml +++ b/core/res/src/main/res/values-ru/strings.xml @@ -798,6 +798,7 @@ Отзыв Разблокировать Разблокировка + Сумма для стейкинга должна быть не менее %s Сумма превышает застейканный баланс Вывод из стейкинга Завершение стейкинга diff --git a/core/res/src/main/res/values-uk-rUA/strings.xml b/core/res/src/main/res/values-uk-rUA/strings.xml index 73f301ed3c..18f9697aaa 100644 --- a/core/res/src/main/res/values-uk-rUA/strings.xml +++ b/core/res/src/main/res/values-uk-rUA/strings.xml @@ -800,6 +800,7 @@ Розблокування Розблокувати Розблокування + Сума для стейкінгу має бути не менше %s Сума перевищує баланс стейкінгу Вивід зі стейкінгу Зняти зі стейкінгу diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml index 0e98aa2383..3a507e68ab 100644 --- a/core/res/src/main/res/values/strings.xml +++ b/core/res/src/main/res/values/strings.xml @@ -698,6 +698,7 @@ Name The amount to stake must be at least %s Staking amount will be rounded to %1$s TRX due to network rules. + Unstaking amount will be rounded to %1$s TRX due to network rules. Claim unstaked Stake account fee A staking account is a special account where staked SOL tokens are stored. It is created when you delegate your tokens to a validator to participate in transaction validation and earn rewards. A small fee is charged for creating the staking account, which is returned after the staking is completed. @@ -773,6 +774,7 @@ Rewards Stake locked Stake more + Staked amount You stake %1$s and will be receiving your reward %2$s Tap to unlock Tap to unlock or vote @@ -783,6 +785,7 @@ Unbonding Unlock Unlocking + The amount to unstake must be at least %s Amount exceeds staked balance Unstaked Unstaking diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendStateFactory.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendStateFactory.kt index ddcc7ab29a..0b69329371 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendStateFactory.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendStateFactory.kt @@ -2,10 +2,13 @@ package com.tangem.features.send.impl.presentation.state import com.tangem.blockchain.common.TransactionData import com.tangem.common.ui.amountScreen.converters.AmountStateConverter +import com.tangem.common.ui.amountScreen.converters.MaxEnterAmountConverter +import com.tangem.common.ui.amountScreen.models.AmountParameters import com.tangem.common.ui.amountScreen.models.AmountState import com.tangem.common.ui.notifications.NotificationUM import com.tangem.core.ui.components.currency.icon.converter.CryptoCurrencyToIconStateConverter import com.tangem.core.ui.event.consumedEvent +import com.tangem.core.ui.extensions.stringReference import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.domain.wallets.models.UserWallet @@ -33,14 +36,15 @@ internal class SendStateFactory( private val isTapHelpPreviewEnabledProvider: Provider, ) { private val iconStateConverter by lazy(::CryptoCurrencyToIconStateConverter) + private val maxEnterAmountConverter = MaxEnterAmountConverter() private val amountStateConverter by lazy(LazyThreadSafetyMode.NONE) { AmountStateConverter( clickIntents = clickIntents, appCurrencyProvider = appCurrencyProvider, iconStateConverter = iconStateConverter, - userWalletProvider = userWalletProvider, cryptoCurrencyStatusProvider = cryptoCurrencyStatusProvider, + maxEnterAmountProvider = Provider { maxEnterAmountConverter.convert(cryptoCurrencyStatusProvider()) }, ) } private val recipientStateConverter by lazy(LazyThreadSafetyMode.NONE) { @@ -79,7 +83,12 @@ internal class SendStateFactory( fun getReadyState(): SendUiState { val state = currentStateProvider() val amountState = if (state.amountState is AmountState.Empty) { - amountStateConverter.convert("") + amountStateConverter.convert( + AmountParameters( + title = stringReference(userWalletProvider().name), + value = "", + ), + ) } else { state.amountState } @@ -96,7 +105,12 @@ internal class SendStateFactory( fun getReadyState(amount: String, destinationAddress: String, memo: String?): SendUiState { val state = currentStateProvider() val amountState = if (state.amountState is AmountState.Empty) { - amountStateConverter.convert(amount) + amountStateConverter.convert( + AmountParameters( + title = stringReference(userWalletProvider().name), + value = amount, + ), + ) } else { state.amountState } diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fields/SendAmountFieldChangeConverter.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fields/SendAmountFieldChangeConverter.kt index 4b5dda431d..c4f28c469e 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fields/SendAmountFieldChangeConverter.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fields/SendAmountFieldChangeConverter.kt @@ -1,5 +1,6 @@ package com.tangem.features.send.impl.presentation.state.fields +import com.tangem.common.ui.amountScreen.converters.MaxEnterAmountConverter import com.tangem.common.ui.amountScreen.converters.field.AmountFieldChangeTransformer import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.features.send.impl.presentation.state.SendUiState @@ -13,15 +14,19 @@ internal class SendAmountFieldChangeConverter( private val cryptoCurrencyStatusProvider: Provider, ) : Converter { + private val maxEnterAmountConverter = MaxEnterAmountConverter() + override fun convert(value: String): SendUiState { val state = currentStateProvider() val isEditState = stateRouterProvider().isEditState - val amountState = state.getAmountState(isEditState) ?: return state + val amountState = state.getAmountState(isEditState) + + val maxEnterAmount = maxEnterAmountConverter.convert(cryptoCurrencyStatusProvider()) return state.copyWrapped( isEditState = isEditState, sendState = state.sendState?.copy(reduceAmountBy = null), - amountState = AmountFieldChangeTransformer(cryptoCurrencyStatusProvider(), value).transform(amountState), + amountState = AmountFieldChangeTransformer(maxEnterAmount, value).transform(amountState), ) } } \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fields/SendAmountFieldMaxAmountConverter.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fields/SendAmountFieldMaxAmountConverter.kt index 5577388040..6e071d34d8 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fields/SendAmountFieldMaxAmountConverter.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fields/SendAmountFieldMaxAmountConverter.kt @@ -1,6 +1,7 @@ package com.tangem.features.send.impl.presentation.state.fields -import com.tangem.common.ui.amountScreen.converters.field.AmountFieldMaxAmountTransformer +import com.tangem.common.ui.amountScreen.converters.MaxEnterAmountConverter +import com.tangem.common.ui.amountScreen.converters.field.AmountFieldSetMaxAmountTransformer import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.features.send.impl.presentation.state.SendUiState import com.tangem.features.send.impl.presentation.state.StateRouter @@ -14,6 +15,8 @@ internal class SendAmountFieldMaxAmountConverter( private val cryptoCurrencyStatusProvider: Provider, ) : Converter { + private val maxEnterAmountConverter = MaxEnterAmountConverter() + override fun convert(value: Unit): SendUiState { val state = currentStateProvider() val cryptoCurrencyStatus = cryptoCurrencyStatusProvider() @@ -23,10 +26,12 @@ internal class SendAmountFieldMaxAmountConverter( val decimalCryptoValue = cryptoCurrencyStatus.value.amount if (decimalCryptoValue.isNullOrZero()) return state + val maxEnterAmount = maxEnterAmountConverter.convert(cryptoCurrencyStatus) + return state.copyWrapped( isEditState = isEditState, sendState = state.sendState?.copy(reduceAmountBy = null), - amountState = AmountFieldMaxAmountTransformer(cryptoCurrencyStatusProvider()).transform(amountState), + amountState = AmountFieldSetMaxAmountTransformer(maxEnterAmount).transform(amountState), ) } } \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/SendScreen.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/SendScreen.kt index 3324d89836..c588753385 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/SendScreen.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/SendScreen.kt @@ -88,7 +88,7 @@ private fun SendAppBar(uiState: SendUiState, currentState: SendUiCurrentScreen) -> resourceReference(R.string.common_fee_selector_title) to null SendUiStateType.Send -> if (uiState.sendState?.isSuccess == false) { resourceReference(R.string.send_summary_title, wrappedList(uiState.cryptoCurrencyName)) to - (uiState.amountState as? AmountState.Data)?.walletName + (uiState.amountState as? AmountState.Data)?.title } else { null to null } @@ -108,7 +108,7 @@ private fun SendAppBar(uiState: SendUiState, currentState: SendUiCurrentScreen) } AppBarWithBackButtonAndIcon( text = titleRes?.resolveReference(), - subtitle = subtitleRes, + subtitle = subtitleRes?.resolveReference(), onBackClick = uiState.clickIntents::onCloseClick, onIconClick = uiState.clickIntents::onQrCodeScanClick, backIconRes = backIcon, diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/InnerYieldBalanceState.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/InnerYieldBalanceState.kt index b6e5bef985..7fa7d42377 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/InnerYieldBalanceState.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/InnerYieldBalanceState.kt @@ -30,9 +30,10 @@ internal data class BalanceState( val subtitle: TextReference?, val isClickable: Boolean, val cryptoValue: String, - val cryptoDecimal: BigDecimal, - val cryptoAmount: TextReference, - val fiatAmount: TextReference, + val cryptoAmount: BigDecimal, + val formattedCryptoAmount: TextReference, + val fiatAmount: BigDecimal?, + val formattedFiatAmount: TextReference, val rawCurrencyId: String?, val validator: Yield.Validator?, val pendingActions: ImmutableList, diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/StakingStateController.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/StakingStateController.kt index dd8919a82c..71d3c67900 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/StakingStateController.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/StakingStateController.kt @@ -75,6 +75,7 @@ internal class StakingStateController @Inject constructor( walletName = "", cryptoCurrencyName = "", cryptoCurrencySymbol = "", + cryptoCurrencyNetworkId = "", currentStep = StakingStep.InitialInfo, initialInfoState = StakingStates.InitialInfoState.Empty(), amountState = AmountState.Empty(), @@ -86,6 +87,7 @@ internal class StakingStateController @Inject constructor( bottomSheetConfig = null, actionType = StakingActionCommonType.Enter, buttonsState = NavigationButtonsState.Empty, + balanceState = null, ) } } \ No newline at end of file diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/StakingStateRouter.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/StakingStateRouter.kt index bcddf3b0df..48823d364f 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/StakingStateRouter.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/StakingStateRouter.kt @@ -5,6 +5,7 @@ import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.domain.staking.model.stakekit.action.StakingActionCommonType import com.tangem.domain.staking.analytics.StakingAnalyticsEvent import com.tangem.features.staking.impl.analytics.utils.StakingAnalyticSender +import com.tangem.lib.crypto.BlockchainUtils.isSolana internal class StakingStateRouter( private val appRouter: AppRouter, @@ -24,8 +25,13 @@ internal class StakingStateRouter( when (stateController.value.currentStep) { StakingStep.InitialInfo -> when (stateController.value.actionType) { StakingActionCommonType.Enter -> showAmount() + // TODO staking [REDACTED_TASK_KEY] support solana multisize hashes signing + StakingActionCommonType.Exit -> if (isSolana(stateController.value.cryptoCurrencyNetworkId)) { + showConfirmation() + } else { + showAmount() + } StakingActionCommonType.Pending.Other, - StakingActionCommonType.Exit, StakingActionCommonType.Pending.Rewards, -> showConfirmation() StakingActionCommonType.Pending.Restake -> showRestakeValidators() diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/StakingUiState.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/StakingUiState.kt index 0613d78e25..470048621d 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/StakingUiState.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/StakingUiState.kt @@ -29,6 +29,7 @@ internal data class StakingUiState( val walletName: String, val cryptoCurrencyName: String, val cryptoCurrencySymbol: String, + val cryptoCurrencyNetworkId: String, val currentStep: StakingStep, val initialInfoState: StakingStates.InitialInfoState, val amountState: AmountState, @@ -40,6 +41,7 @@ internal data class StakingUiState( val actionType: StakingActionCommonType, val buttonsState: NavigationButtonsState, val event: StateEvent, + val balanceState: BalanceState?, ) { fun copyWrapped( diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/converters/BalanceItemConverter.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/converters/BalanceItemConverter.kt index 3268482b57..8051fb9e54 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/converters/BalanceItemConverter.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/converters/BalanceItemConverter.kt @@ -47,11 +47,12 @@ internal class BalanceItemConverter( subtitle = getSubtitle(value), type = value.type, cryptoValue = cryptoAmount.parseBigDecimal(cryptoCurrency.decimals), - cryptoDecimal = cryptoAmount, - cryptoAmount = stringReference( + cryptoAmount = cryptoAmount, + formattedCryptoAmount = stringReference( cryptoAmount.format { crypto(cryptoCurrency) }, ), - fiatAmount = stringReference( + fiatAmount = fiatAmount, + formattedFiatAmount = stringReference( BigDecimalFormatter.formatFiatAmount( fiatAmount = fiatAmount, fiatCurrencyCode = appCurrency.code, diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/converters/RewardsValidatorStateConverter.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/converters/RewardsValidatorStateConverter.kt index b03ba279ed..b151140d55 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/converters/RewardsValidatorStateConverter.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/converters/RewardsValidatorStateConverter.kt @@ -71,7 +71,7 @@ internal class RewardsValidatorStateConverter( crypto(cryptoCurrency) }, ) - val fiatAmount = stringReference( + val formattedFiatAmount = stringReference( BigDecimalFormatter.formatFiatAmount( fiatAmount = fiatValue, fiatCurrencyCode = appCurrency.code, @@ -85,9 +85,10 @@ internal class RewardsValidatorStateConverter( title = stringReference(this.name), subtitle = null, cryptoValue = cryptoValue.parseBigDecimal(cryptoCurrency.decimals), - cryptoDecimal = cryptoValue, - cryptoAmount = cryptoAmount, - fiatAmount = fiatAmount, + cryptoAmount = cryptoValue, + formattedCryptoAmount = cryptoAmount, + fiatAmount = fiatValue, + formattedFiatAmount = formattedFiatAmount, rawCurrencyId = cryptoCurrency.id.rawCurrencyId, pendingActions = balance.pendingActions.toPersistentList(), isClickable = true, diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/converters/YieldBalancesConverter.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/converters/YieldBalancesConverter.kt index cb9490bdc8..29ccbc1d29 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/converters/YieldBalancesConverter.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/converters/YieldBalancesConverter.kt @@ -63,7 +63,7 @@ internal class YieldBalancesConverter( private fun List.mapBalances() = asSequence() .filterNot { it.amount.isZero() || it.type == BalanceType.REWARDS } .mapNotNull(balanceItemConverter::convert) - .sortedByDescending { it.cryptoDecimal } + .sortedByDescending { it.cryptoAmount } .sortedBy { it.type.order } .toPersistentList() diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/helpers/StakingFeeTransactionLoader.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/helpers/StakingFeeTransactionLoader.kt index 207436cf09..9167845687 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/helpers/StakingFeeTransactionLoader.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/helpers/StakingFeeTransactionLoader.kt @@ -20,7 +20,7 @@ import com.tangem.domain.transaction.usecase.GetFeeUseCase import com.tangem.domain.wallets.models.UserWallet import com.tangem.features.staking.impl.presentation.state.StakingStateController import com.tangem.features.staking.impl.presentation.state.StakingStates -import com.tangem.features.staking.impl.presentation.state.utils.isComposePendingActions +import com.tangem.features.staking.impl.presentation.state.utils.isCompositePendingActions import com.tangem.utils.extensions.orZero import dagger.assisted.Assisted import dagger.assisted.AssistedFactory @@ -95,7 +95,11 @@ internal class StakingFeeTransactionLoader @AssistedInject constructor( val sourceAddress = cryptoCurrencyStatus.value.networkAddress?.defaultAddress?.value ?: error("No available address") - val gasEstimate = if (isComposePendingActions(cryptoCurrencyStatus.currency.network.id.value, pendingActions)) { + val gasEstimate = if (isCompositePendingActions( + networkId = cryptoCurrencyStatus.currency.network.id.value, + pendingActions = pendingActions, + ) + ) { val result = coroutineScope { pendingActions?.map { action -> async { diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/helpers/StakingTransactionSender.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/helpers/StakingTransactionSender.kt index 3a7bacaf46..3ccad868cc 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/helpers/StakingTransactionSender.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/helpers/StakingTransactionSender.kt @@ -20,7 +20,7 @@ import com.tangem.domain.utils.convertToSdkAmount import com.tangem.domain.wallets.models.UserWallet import com.tangem.features.staking.impl.presentation.state.* import com.tangem.features.staking.impl.presentation.state.utils.checkAndCalculateSubtractedAmount -import com.tangem.features.staking.impl.presentation.state.utils.isComposePendingActions +import com.tangem.features.staking.impl.presentation.state.utils.isCompositePendingActions import com.tangem.utils.extensions.orZero import dagger.assisted.Assisted import dagger.assisted.AssistedFactory @@ -96,7 +96,7 @@ internal class StakingTransactionSender @AssistedInject constructor( confirmationState: StakingStates.ConfirmationState.Data, onConstructError: (StakingError) -> Unit, ) = coroutineScope { - val isComposePendingActions = isComposePendingActions( + val isComposePendingActions = isCompositePendingActions( cryptoCurrencyStatus.currency.network.id.value, confirmationState.pendingActions, ) diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/previewdata/InitialStakingStatePreview.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/previewdata/InitialStakingStatePreview.kt index 4d3dc0ad83..293ba68c01 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/previewdata/InitialStakingStatePreview.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/previewdata/InitialStakingStatePreview.kt @@ -73,9 +73,10 @@ internal object InitialStakingStatePreview { groupId = "groupId", title = stringReference("Binance"), cryptoValue = "100", - cryptoAmount = stringReference("100 SOL"), - cryptoDecimal = "100".toBigDecimal(), - fiatAmount = stringReference("100 $"), + formattedCryptoAmount = stringReference("100 SOL"), + cryptoAmount = "100".toBigDecimal(), + fiatAmount = null, + formattedFiatAmount = stringReference("100 $"), rawCurrencyId = null, validator = Yield.Validator( address = "address", diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/stub/StakingClickIntentsStub.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/stub/StakingClickIntentsStub.kt index 9da7141f5a..514a94fc76 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/stub/StakingClickIntentsStub.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/stub/StakingClickIntentsStub.kt @@ -26,7 +26,7 @@ internal object StakingClickIntentsStub : StakingClickIntents { override fun onInfoClick(infoType: InfoType) {} - override fun onEnterClick() {} + override fun onAmountEnterClick() {} override fun onAmountValueChange(value: String) {} diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetAmountDataTransformer.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetAmountDataTransformer.kt new file mode 100644 index 0000000000..434b40908c --- /dev/null +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetAmountDataTransformer.kt @@ -0,0 +1,55 @@ +package com.tangem.features.staking.impl.presentation.state.transformers + +import com.tangem.common.ui.amountScreen.converters.AmountStateConverter +import com.tangem.common.ui.amountScreen.models.AmountParameters +import com.tangem.common.ui.amountScreen.models.MaxEnterAmount +import com.tangem.core.ui.components.currency.icon.converter.CryptoCurrencyToIconStateConverter +import com.tangem.core.ui.extensions.resourceReference +import com.tangem.core.ui.extensions.stringReference +import com.tangem.domain.appcurrency.model.AppCurrency +import com.tangem.domain.staking.model.stakekit.action.StakingActionCommonType +import com.tangem.domain.tokens.model.CryptoCurrencyStatus +import com.tangem.domain.wallets.models.UserWallet +import com.tangem.features.staking.impl.R +import com.tangem.features.staking.impl.presentation.state.* +import com.tangem.features.staking.impl.presentation.viewmodel.StakingClickIntents +import com.tangem.utils.Provider +import com.tangem.utils.transformer.Transformer + +internal class SetAmountDataTransformer( + private val clickIntents: StakingClickIntents, + private val cryptoCurrencyStatusProvider: Provider, + private val userWalletProvider: Provider, + private val appCurrencyProvider: Provider, + private val maxEnterAmountProvider: Provider, +) : Transformer { + + private val iconStateConverter by lazy(::CryptoCurrencyToIconStateConverter) + + private val amountStateConverter by lazy(LazyThreadSafetyMode.NONE) { + AmountStateConverter( + clickIntents = clickIntents, + cryptoCurrencyStatusProvider = cryptoCurrencyStatusProvider, + appCurrencyProvider = appCurrencyProvider, + iconStateConverter = iconStateConverter, + maxEnterAmountProvider = maxEnterAmountProvider, + ) + } + + override fun transform(prevState: StakingUiState): StakingUiState { + val title = if (prevState.actionType == StakingActionCommonType.Exit) { + resourceReference(R.string.staking_staked_amount) + } else { + stringReference(userWalletProvider().name) + } + + return prevState.copy( + amountState = amountStateConverter.convert( + AmountParameters( + title = title, + value = "", + ), + ), + ) + } +} \ No newline at end of file diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetButtonsStateTransformer.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetButtonsStateTransformer.kt index 5d7d0260e8..e989a47670 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetButtonsStateTransformer.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetButtonsStateTransformer.kt @@ -148,7 +148,7 @@ internal class SetButtonsStateTransformer( StakingStep.Validators, StakingStep.RestakeValidator, -> clickIntents.onNextClick() - StakingStep.Amount -> clickIntents.onEnterClick() + StakingStep.Amount -> clickIntents.onAmountEnterClick() StakingStep.Confirmation -> onConfirmationClick() StakingStep.RewardsValidators -> Unit } diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetConfirmationStateEmptyTransformer.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetConfirmationStateEmptyTransformer.kt index 84cd59a951..4bf8d5ce5d 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetConfirmationStateEmptyTransformer.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetConfirmationStateEmptyTransformer.kt @@ -11,6 +11,7 @@ internal object SetConfirmationStateEmptyTransformer : Transformer? = null, private val pendingAction: PendingAction? = pendingActions?.firstOrNull(), + ) : Transformer { private val networkId get() = cryptoCurrencyStatus.currency.network.id.value private val isComposePendingActions - get() = isComposePendingActions(networkId, pendingActions) + get() = isCompositePendingActions(networkId, pendingActions) private val isTronStakedBalance get() = isTronStakedBalance(networkId, pendingAction) - private val isExit: Boolean + private val isImplicitExit: Boolean get() = pendingAction == null && pendingActions?.isEmpty() == true || isTronStakedBalance override fun transform(prevState: StakingUiState): StakingUiState { val actionType = when { isEnter -> StakingActionCommonType.Enter - isExit -> StakingActionCommonType.Exit + isImplicitExit || isExplicitExit -> StakingActionCommonType.Exit else -> when (pendingAction?.type) { StakingActionType.STAKE -> StakingActionCommonType.Enter StakingActionType.UNSTAKE -> StakingActionCommonType.Exit @@ -54,6 +57,7 @@ internal class SetConfirmationStateInitTransformer( return prevState.copy( actionType = actionType, + balanceState = balanceState, confirmationState = StakingStates.ConfirmationState.Data( isPrimaryButtonEnabled = false, innerState = InnerConfirmationStakingState.ASSENT, diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetInitialDataStateTransformer.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetInitialDataStateTransformer.kt index 9689522cd7..e4cc55d35c 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetInitialDataStateTransformer.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetInitialDataStateTransformer.kt @@ -2,7 +2,9 @@ package com.tangem.features.staking.impl.presentation.state.transformers import com.tangem.common.extensions.remove import com.tangem.common.ui.amountScreen.converters.AmountStateConverter +import com.tangem.common.ui.amountScreen.models.AmountParameters import com.tangem.common.ui.amountScreen.models.AmountState +import com.tangem.common.ui.amountScreen.models.MaxEnterAmount import com.tangem.core.ui.components.currency.icon.converter.CryptoCurrencyToIconStateConverter import com.tangem.core.ui.components.list.RoundedListWithDividersItemData import com.tangem.core.ui.extensions.* @@ -39,6 +41,7 @@ internal class SetInitialDataStateTransformer( private val userWalletProvider: Provider, private val appCurrencyProvider: Provider, private val balancesToShowProvider: Provider>, + private val maxEnterAmountProvider: Provider, ) : Transformer { private val iconStateConverter by lazy(::CryptoCurrencyToIconStateConverter) @@ -48,8 +51,8 @@ internal class SetInitialDataStateTransformer( clickIntents = clickIntents, cryptoCurrencyStatusProvider = cryptoCurrencyStatusProvider, appCurrencyProvider = appCurrencyProvider, - userWalletProvider = userWalletProvider, iconStateConverter = iconStateConverter, + maxEnterAmountProvider = maxEnterAmountProvider, ) } @@ -72,6 +75,7 @@ internal class SetInitialDataStateTransformer( title = TextReference.EMPTY, cryptoCurrencyName = cryptoCurrency.name, cryptoCurrencySymbol = cryptoCurrency.symbol, + cryptoCurrencyNetworkId = cryptoCurrency.network.id.value, clickIntents = clickIntents, currentStep = StakingStep.InitialInfo, initialInfoState = createInitialInfoState(), @@ -207,7 +211,12 @@ internal class SetInitialDataStateTransformer( } private fun createInitialAmountState(): AmountState { - return amountStateConverter.convert("") + return amountStateConverter.convert( + AmountParameters( + title = stringReference(userWalletProvider().name), + value = "", + ), + ) } private fun getAprRange(validators: List): TextReference { diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/amount/AmountChangeStateTransformer.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/amount/AmountChangeStateTransformer.kt index f91d878456..cd74d47116 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/amount/AmountChangeStateTransformer.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/amount/AmountChangeStateTransformer.kt @@ -1,7 +1,10 @@ package com.tangem.features.staking.impl.presentation.state.transformers.amount +import com.tangem.common.ui.amountScreen.converters.MaxEnterAmountConverter import com.tangem.common.ui.amountScreen.converters.field.AmountFieldChangeTransformer +import com.tangem.common.ui.amountScreen.models.MaxEnterAmount import com.tangem.domain.staking.model.stakekit.Yield +import com.tangem.domain.staking.model.stakekit.action.StakingActionCommonType import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.features.staking.impl.presentation.state.StakingUiState import com.tangem.utils.transformer.Transformer @@ -12,11 +15,21 @@ internal class AmountChangeStateTransformer( private val yield: Yield, ) : Transformer { + private val maxEnterAmountConverter = MaxEnterAmountConverter() + override fun transform(prevState: StakingUiState): StakingUiState { - val updatedAmountState = AmountFieldChangeTransformer( - cryptoCurrencyStatus, - value, - ).transform(prevState.amountState) + val actionType = prevState.actionType + val maxEnterAmount = if (actionType == StakingActionCommonType.Exit) { + MaxEnterAmount( + amount = prevState.balanceState?.cryptoAmount, + fiatAmount = prevState.balanceState?.fiatAmount, + fiatRate = cryptoCurrencyStatus.value.fiatRate, + ) + } else { + maxEnterAmountConverter.convert(cryptoCurrencyStatus) + } + + val updatedAmountState = AmountFieldChangeTransformer(maxEnterAmount, value).transform(prevState.amountState) return prevState.copy( amountState = AmountRequirementStateTransformer( diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/amount/AmountMaxValueStateTransformer.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/amount/AmountMaxValueStateTransformer.kt index 088528794d..034398c337 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/amount/AmountMaxValueStateTransformer.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/amount/AmountMaxValueStateTransformer.kt @@ -1,18 +1,35 @@ package com.tangem.features.staking.impl.presentation.state.transformers.amount -import com.tangem.common.ui.amountScreen.converters.field.AmountFieldMaxAmountTransformer +import com.tangem.common.ui.amountScreen.converters.MaxEnterAmountConverter +import com.tangem.common.ui.amountScreen.converters.field.AmountFieldSetMaxAmountTransformer +import com.tangem.common.ui.amountScreen.models.MaxEnterAmount import com.tangem.domain.staking.model.stakekit.Yield +import com.tangem.domain.staking.model.stakekit.action.StakingActionCommonType import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.features.staking.impl.presentation.state.StakingUiState import com.tangem.utils.transformer.Transformer internal class AmountMaxValueStateTransformer( private val cryptoCurrencyStatus: CryptoCurrencyStatus, + private val actionType: StakingActionCommonType, private val yield: Yield, ) : Transformer { + private val maxEnterAmountConverter = MaxEnterAmountConverter() + override fun transform(prevState: StakingUiState): StakingUiState { - val updatedAmountState = AmountFieldMaxAmountTransformer(cryptoCurrencyStatus).transform(prevState.amountState) + val maxEnterAmount = if (actionType == StakingActionCommonType.Exit) { + MaxEnterAmount( + amount = prevState.balanceState?.cryptoAmount, + fiatAmount = prevState.balanceState?.fiatAmount, + fiatRate = cryptoCurrencyStatus.value.fiatRate, + ) + } else { + maxEnterAmountConverter.convert(cryptoCurrencyStatus) + } + + val updatedAmountState = AmountFieldSetMaxAmountTransformer(maxEnterAmount) + .transform(prevState.amountState) return prevState.copy( amountState = AmountRequirementStateTransformer( cryptoCurrencyStatus = cryptoCurrencyStatus, diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/amount/AmountRequirementStateTransformer.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/amount/AmountRequirementStateTransformer.kt index 88ac2c09f5..199707ada1 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/amount/AmountRequirementStateTransformer.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/amount/AmountRequirementStateTransformer.kt @@ -61,10 +61,17 @@ internal class AmountRequirementStateTransformer( }, ), ) - isIntegerOnlyError -> resourceReference( - R.string.staking_amount_tron_integer_error, - wrappedList(value), - ) + isIntegerOnlyError -> when (actionType) { + StakingActionCommonType.Enter -> resourceReference( + R.string.staking_amount_tron_integer_error, + wrappedList(value), + ) + StakingActionCommonType.Exit -> resourceReference( + R.string.staking_amount_tron_integer_error_unstaking, + wrappedList(value), + ) + else -> TODO() + } else -> TextReference.EMPTY } val isError = amountState.amountTextField.isError || isRequirementError @@ -101,12 +108,12 @@ internal class AmountRequirementStateTransformer( private fun isIntegerOnlyError(amountState: AmountState.Data, actionType: StakingActionCommonType): Boolean { val cryptoAmountValue = amountState.amountTextField.cryptoAmount.value ?: return false - val isEnter = actionType == StakingActionCommonType.Enter + val isEnterOrExit = actionType == StakingActionCommonType.Enter || actionType == StakingActionCommonType.Exit val isTron = isTron(cryptoCurrencyStatus.currency.network.id.value) val isIntegerOnly = cryptoAmountValue.isZero() || cryptoAmountValue.remainder(BigDecimal.ONE).isZero() - return isEnter && isTron && !isIntegerOnly + return isEnterOrExit && isTron && !isIntegerOnly } data class Data( diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/utils/StakingPendingActionUtils.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/utils/StakingPendingActionUtils.kt index 2a8113944f..558aa4252e 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/utils/StakingPendingActionUtils.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/utils/StakingPendingActionUtils.kt @@ -35,12 +35,12 @@ internal fun StakingActionType?.getPendingActionTitle(): TextReference = when (t internal fun isSingleAction(networkId: String, activeStake: BalanceState): Boolean { val isSingleAction = activeStake.pendingActions.size <= 1 // Either single or none pending actions - val isComposePendingActions = isComposePendingActions(networkId, activeStake.pendingActions) + val isCompositePendingActions = isCompositePendingActions(networkId, activeStake.pendingActions) val isBscRestake = isBSC(networkId) && activeStake.pendingActions.any { it.type == StakingActionType.RESTAKE } - return isSingleAction && !isBscRestake || isComposePendingActions + return isSingleAction && !isBscRestake || isCompositePendingActions } internal fun withStubUnstakeAction(networkId: String, activeStake: BalanceState) = if (isBSC(networkId)) { @@ -59,7 +59,7 @@ internal fun isTronStakedBalance(networkId: String, pendingAction: PendingAction return isTron(networkId) && pendingAction?.type == StakingActionType.REVOTE } -internal fun isComposePendingActions(networkId: String, pendingActions: ImmutableList?): Boolean { +internal fun isCompositePendingActions(networkId: String, pendingActions: ImmutableList?): Boolean { return when { isSolana(networkId) -> pendingActions?.any { it.type == StakingActionType.WITHDRAW } == true else -> false diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingClaimRewardsValidatorContent.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingClaimRewardsValidatorContent.kt index faaf8c499c..180306b99e 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingClaimRewardsValidatorContent.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingClaimRewardsValidatorContent.kt @@ -47,8 +47,8 @@ internal fun StakingClaimRewardsValidatorContent( ) }, ), - infoTitle = item.fiatAmount, - infoSubtitle = item.cryptoAmount, + infoTitle = item.formattedFiatAmount, + infoSubtitle = item.formattedCryptoAmount, imageUrl = item.validator?.image.orEmpty(), onImageError = { ValidatorImagePlaceholder() }, modifier = modifier diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingInitialInfoContent.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingInitialInfoContent.kt index 2895d3619c..d0d364184d 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingInitialInfoContent.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingInitialInfoContent.kt @@ -270,8 +270,8 @@ private fun ActiveStakingBlock( InputRowImageInfo( subtitle = balance.title, caption = balance.subtitle ?: balance.getAprText(), - infoTitle = balance.fiatAmount.orMaskWithStars(isBalanceHidden), - infoSubtitle = balance.cryptoAmount.orMaskWithStars(isBalanceHidden), + infoTitle = balance.formattedFiatAmount.orMaskWithStars(isBalanceHidden), + infoSubtitle = balance.formattedCryptoAmount.orMaskWithStars(isBalanceHidden), imageUrl = balance.getImage(), iconRes = icon, iconTint = iconTint, diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/viewmodel/StakingClickIntents.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/viewmodel/StakingClickIntents.kt index 7e23d53fc6..1b4d3eeee4 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/viewmodel/StakingClickIntents.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/viewmodel/StakingClickIntents.kt @@ -26,7 +26,7 @@ internal interface StakingClickIntents : AmountScreenClickIntents { fun onInfoClick(infoType: InfoType) - fun onEnterClick() + fun onAmountEnterClick() fun getFee() diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/viewmodel/StakingViewModel.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/viewmodel/StakingViewModel.kt index 998261a393..a15947fe0e 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/viewmodel/StakingViewModel.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/viewmodel/StakingViewModel.kt @@ -11,6 +11,7 @@ import com.tangem.common.routing.AppRoute import com.tangem.common.routing.bundle.unbundle import com.tangem.common.ui.amountScreen.converters.AmountReduceByTransformer import com.tangem.common.ui.amountScreen.models.AmountState +import com.tangem.common.ui.amountScreen.models.MaxEnterAmount import com.tangem.common.ui.bottomsheet.permission.state.ApproveType import com.tangem.common.ui.bottomsheet.permission.state.GiveTxPermissionBottomSheetConfig import com.tangem.common.ui.notifications.NotificationUM @@ -31,10 +32,6 @@ import com.tangem.domain.staking.InvalidatePendingTransactionsUseCase import com.tangem.domain.staking.IsAnyTokenStakedUseCase import com.tangem.domain.staking.IsApproveNeededUseCase import com.tangem.domain.staking.model.StakingApproval -import com.tangem.domain.staking.model.stakekit.BalanceItem -import com.tangem.domain.staking.model.stakekit.PendingAction -import com.tangem.domain.staking.model.stakekit.Yield -import com.tangem.domain.staking.model.stakekit.YieldBalance import com.tangem.domain.staking.model.stakekit.action.StakingAction import com.tangem.domain.staking.model.stakekit.action.StakingActionCommonType import com.tangem.domain.staking.model.stakekit.transaction.StakingTransaction @@ -52,6 +49,7 @@ import com.tangem.domain.wallets.models.UserWalletId import com.tangem.domain.wallets.usecase.GetUserWalletUseCase import com.tangem.domain.staking.analytics.StakeScreenSource import com.tangem.domain.staking.analytics.StakingAnalyticsEvent +import com.tangem.domain.staking.model.stakekit.* import com.tangem.features.staking.impl.analytics.StakingParamsInterceptor import com.tangem.features.staking.impl.analytics.utils.StakingAnalyticSender import com.tangem.features.staking.impl.navigation.InnerStakingRouter @@ -156,6 +154,14 @@ internal class StakingViewModel @Inject constructor( ).getOrElse { emptyList() } } + private val maxEnterAmount: MaxEnterAmount + get() = + MaxEnterAmount( + amount = uiState.value.balanceState?.cryptoAmount, + fiatAmount = uiState.value.balanceState?.fiatAmount, + fiatRate = cryptoCurrencyStatus.value.fiatRate, + ) + private var isInitialInfoAnalyticSent: Boolean = false private val balanceUpdater by lazy(LazyThreadSafetyMode.NONE) { @@ -227,6 +233,18 @@ internal class StakingViewModel @Inject constructor( } override fun onNextClick(balanceState: BalanceState?) { + if (value.currentStep == StakingStep.InitialInfo && balanceState == null) { + stateController.update( + SetConfirmationStateInitTransformer( + isEnter = true, + isExplicitExit = false, + balanceState = null, + cryptoCurrencyStatus = cryptoCurrencyStatus, + stakingApproval = stakingApproval, + stakingAllowance = stakingAllowance, + ), + ) + } stakingStateRouter.onNextClick() } @@ -350,24 +368,20 @@ internal class StakingViewModel @Inject constructor( ) } - override fun onEnterClick() { + override fun onAmountEnterClick() { if (yield.preferredValidators.isEmpty()) { stateController.updateEvent( StakingEvent.ShowAlert(StakingAlertUM.NoAvailableValidators), ) } else { - stateController.updateAll( - SetConfirmationStateInitTransformer( - isEnter = true, - cryptoCurrencyStatus = cryptoCurrencyStatus, - stakingApproval = stakingApproval, - stakingAllowance = stakingAllowance, - ), - ValidatorSelectChangeTransformer( - selectedValidator = null, - yield = yield, - ), - ) + if (uiState.value.actionType == StakingActionCommonType.Enter) { + stateController.updateAll( + ValidatorSelectChangeTransformer( + selectedValidator = null, + yield = yield, + ), + ) + } onNextClick() } } @@ -382,7 +396,13 @@ internal class StakingViewModel @Inject constructor( override fun onMaxValueClick() { analyticsEventHandler.send(StakingAnalyticsEvent.ButtonMax) - stateController.update(AmountMaxValueStateTransformer(cryptoCurrencyStatus, yield)) + stateController.update( + AmountMaxValueStateTransformer( + cryptoCurrencyStatus = cryptoCurrencyStatus, + actionType = uiState.value.actionType, + yield = yield, + ), + ) } override fun onCurrencyChangeClick(isFiat: Boolean) { @@ -439,23 +459,27 @@ internal class StakingViewModel @Inject constructor( val networkId = cryptoCurrencyStatus.currency.network.id.value if (isSingleAction(networkId, activeStake)) { prepareForConfirmation( + balanceType = activeStake.type, pendingActions = activeStake.pendingActions, + balanceState = activeStake, validator = activeStake.validator, amountValue = activeStake.cryptoValue, ) - onNextClick(balanceState = activeStake) + onNextClick(activeStake) } else { stateController.update( ShowActionSelectorBottomSheetTransformer( pendingActions = withStubUnstakeAction(networkId, activeStake), onActionSelect = { action -> prepareForConfirmation( + balanceType = activeStake.type, pendingAction = action, + balanceState = activeStake, validator = activeStake.validator, amountValue = activeStake.cryptoValue, ) stateController.update(DismissBottomSheetStateTransformer) - onNextClick(balanceState = activeStake) + onNextClick(activeStake) }, onDismiss = { stateController.update(DismissBottomSheetStateTransformer) }, ), @@ -889,12 +913,15 @@ internal class StakingViewModel @Inject constructor( userWalletProvider = Provider { userWallet }, appCurrencyProvider = Provider { appCurrency }, balancesToShowProvider = Provider { balancesToShow }, + maxEnterAmountProvider = Provider { maxEnterAmount }, ), SetConfirmationStateEmptyTransformer, ) } private fun prepareForConfirmation( + balanceType: BalanceType, + balanceState: BalanceState, pendingActions: ImmutableList = persistentListOf(), pendingAction: PendingAction? = pendingActions.firstOrNull(), validator: Yield.Validator?, @@ -903,6 +930,8 @@ internal class StakingViewModel @Inject constructor( stateController.updateAll( SetConfirmationStateInitTransformer( isEnter = false, + isExplicitExit = balanceType == BalanceType.STAKED, + balanceState = balanceState, cryptoCurrencyStatus = cryptoCurrencyStatus, stakingApproval = stakingApproval, pendingActions = pendingActions, @@ -913,6 +942,13 @@ internal class StakingViewModel @Inject constructor( selectedValidator = validator, yield = yield, ), + SetAmountDataTransformer( + clickIntents = this, + cryptoCurrencyStatusProvider = Provider { cryptoCurrencyStatus }, + userWalletProvider = Provider { userWallet }, + appCurrencyProvider = Provider { appCurrency }, + maxEnterAmountProvider = Provider { maxEnterAmount }, + ), AmountChangeStateTransformer( cryptoCurrencyStatus = cryptoCurrencyStatus, value = amountValue, From 00107432cf91325fe0c3d41f940d4b431665530a Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 1 Nov 2024 19:29:07 +0300 Subject: [PATCH 077/106] Updated on 2026-08-14 --- gradle/dependencies.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gradle/dependencies.toml b/gradle/dependencies.toml index ecf6a68855..d78017f8a3 100644 --- a/gradle/dependencies.toml +++ b/gradle/dependencies.toml @@ -88,9 +88,9 @@ markdownComposeView = "0.5.4" # endregion Other libraries # region Tangem -tangemBlockchainSdk = "develop-848" +tangemBlockchainSdk = "release-app_5.18-849" #tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds -tangemCardSdk = "develop-395" +tangemCardSdk = "release-app_5.18-396" #tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^ tangemVico = "2.0.0-alpha.25-tangem17" #tangemVico = "0.0.1" # Keep it! - used for local builds ^ From 8bc33fe272a6663715a2afe07220850304fb2450 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 4 Nov 2024 12:32:43 +0500 Subject: [PATCH 078/106] Updated on 2026-08-14 --- .../src/main/assets/contract_methods.json | 40 +++++++++++++++++++ .../utils/SdkTransactionTypeConverter.kt | 22 +++++++--- .../domain/txhistory/models/TxHistoryItem.kt | 12 +++--- ...tailsTxHistoryTransactionStateConverter.kt | 26 ++++++------ .../converter/TxHistoryItemStateConverter.kt | 26 ++++++------ 5 files changed, 89 insertions(+), 37 deletions(-) diff --git a/domain/legacy/src/main/assets/contract_methods.json b/domain/legacy/src/main/assets/contract_methods.json index f7bad0ed28..6a8d6dafc4 100644 --- a/domain/legacy/src/main/assets/contract_methods.json +++ b/domain/legacy/src/main/assets/contract_methods.json @@ -141,5 +141,45 @@ "info":"swapTokensForExactETC", "source":"https://etc.blockscout.com/tx/0xd79e03ca6b71529c94d576ff78c55b4371e2ef3ff8f9a47007995e5b9e77f879", "name":"swap" + }, + "0x6ab15071": { + "info": "stakePOL", + "source": "https://etherscan.io/tx/0x490d2f31faa3f2fdb1a9505888761d89aba1dd3dcfeb65f08651d094c1ee5021", + "name": "buyVoucher" + }, + "0xe4457a8a": { + "info": "stakePOL", + "source": "https://etherscan.io/tx/0xb7b67ea0261fe2067e285eb942be6883ef34b3d313160f3ce122cad3e91f95f7", + "name": "buyVoucherPOL" + }, + "0xc83ec04d": { + "info": "stakePOL", + "source": "https://etherscan.io/tx/0x0f7fa7154be5ae9bbe229963e3c0d3eea16b74311c50c4a35f3fcffc9519baed", + "name": "sellVoucher_new" + }, + "0xe570b78b": { + "info": "stakePOL", + "source": "https://etherscan.io/tx/0xde1dda9ff3aed8b1d5feff53f72032cbd106a57c1ae2e7fa80016349e2e68341", + "name": "sellVoucher_newPOL" + }, + "0xe97fddc2": { + "info": "stakePOL", + "source": "https://etherscan.io/tx/0x0478bc1229722c049eed737ef5a56baee6d7bde7ae21afad7a10e029700b378c", + "name": "unstakeClaimTokens_new" + }, + "0x8759c234": { + "info": "stakePOL", + "source": "https://etherscan.io/tx/0x79075778b470358bef4cfc25a74a43e7ae60f1bc292e0bca3ebc163fee8bf0c7", + "name": "unstakeClaimTokens_newPOL" + }, + "0xc7b8981c": { + "info": "stakePOL", + "source": "https://etherscan.io/tx/0xc2df9b41f7b3780a2dd06f0dfff72ecd5d3a5bde3f1d3f0eb926e14ff975e957", + "name": "withdrawRewards" + }, + "0xe0db556b": { + "info": "stakePOL", + "source": "https://etherscan.io/tx/0x96ee64ad31a045982a4776336d852af3c25dc39b6d01bea987aefd460b816773", + "name": "withdrawRewardsPOL" } } diff --git a/domain/legacy/src/main/java/com/tangem/domain/walletmanager/utils/SdkTransactionTypeConverter.kt b/domain/legacy/src/main/java/com/tangem/domain/walletmanager/utils/SdkTransactionTypeConverter.kt index 306c18cf6a..fa74187f5a 100644 --- a/domain/legacy/src/main/java/com/tangem/domain/walletmanager/utils/SdkTransactionTypeConverter.kt +++ b/domain/legacy/src/main/java/com/tangem/domain/walletmanager/utils/SdkTransactionTypeConverter.kt @@ -21,19 +21,19 @@ internal class SdkTransactionTypeConverter( TxHistoryItem.TransactionType.Transfer } is TransactionType.TronStakingTransactionType.FreezeBalanceV2Contract -> { - TxHistoryItem.TransactionType.TronStakingTransactionType.Stake + TxHistoryItem.TransactionType.StakingTransactionType.Stake } is TransactionType.TronStakingTransactionType.UnfreezeBalanceV2Contract -> { - TxHistoryItem.TransactionType.TronStakingTransactionType.Unstake + TxHistoryItem.TransactionType.StakingTransactionType.Unstake } is TransactionType.TronStakingTransactionType.VoteWitnessContract -> { - TxHistoryItem.TransactionType.TronStakingTransactionType.Vote(value.validatorAddress) + TxHistoryItem.TransactionType.StakingTransactionType.Vote(value.validatorAddress) } is TransactionType.TronStakingTransactionType.WithdrawBalanceContract -> { - TxHistoryItem.TransactionType.TronStakingTransactionType.ClaimRewards + TxHistoryItem.TransactionType.StakingTransactionType.ClaimRewards } is TransactionType.TronStakingTransactionType.WithdrawExpireUnfreezeContract -> { - TxHistoryItem.TransactionType.TronStakingTransactionType.Withdraw + TxHistoryItem.TransactionType.StakingTransactionType.Withdraw } } } @@ -43,6 +43,18 @@ internal class SdkTransactionTypeConverter( "transfer" -> TxHistoryItem.TransactionType.Transfer "approve" -> TxHistoryItem.TransactionType.Approve "swap" -> TxHistoryItem.TransactionType.Swap + "buyVoucher", + "buyVoucherPOL", + -> TxHistoryItem.TransactionType.StakingTransactionType.Stake + "sellVoucher_new", + "sellVoucher_newPOL", + -> TxHistoryItem.TransactionType.StakingTransactionType.Unstake + "unstakeClaimTokens_new", + "unstakeClaimTokens_newPOL", + -> TxHistoryItem.TransactionType.StakingTransactionType.Withdraw + "withdrawRewards", + "withdrawRewardsPOL", + -> TxHistoryItem.TransactionType.StakingTransactionType.ClaimRewards null -> TxHistoryItem.TransactionType.UnknownOperation else -> TxHistoryItem.TransactionType.Operation(name = methodName.replaceFirstChar { it.titlecase() }) } diff --git a/domain/txhistory/models/src/main/kotlin/com/tangem/domain/txhistory/models/TxHistoryItem.kt b/domain/txhistory/models/src/main/kotlin/com/tangem/domain/txhistory/models/TxHistoryItem.kt index f96905a1ed..526be8774c 100644 --- a/domain/txhistory/models/src/main/kotlin/com/tangem/domain/txhistory/models/TxHistoryItem.kt +++ b/domain/txhistory/models/src/main/kotlin/com/tangem/domain/txhistory/models/TxHistoryItem.kt @@ -40,12 +40,12 @@ data class TxHistoryItem( data object UnknownOperation : TransactionType data class Operation(val name: String) : TransactionType - sealed interface TronStakingTransactionType : TransactionType { - data class Vote(val validatorAddress: String) : TronStakingTransactionType - data object ClaimRewards : TronStakingTransactionType - data object Stake : TronStakingTransactionType - data object Unstake : TronStakingTransactionType - data object Withdraw : TronStakingTransactionType + sealed interface StakingTransactionType : TransactionType { + data class Vote(val validatorAddress: String) : StakingTransactionType + data object ClaimRewards : StakingTransactionType + data object Stake : StakingTransactionType + data object Unstake : StakingTransactionType + data object Withdraw : StakingTransactionType } } diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/txhistory/TokenDetailsTxHistoryTransactionStateConverter.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/txhistory/TokenDetailsTxHistoryTransactionStateConverter.kt index 4756f994ca..91863e54cd 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/txhistory/TokenDetailsTxHistoryTransactionStateConverter.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/txhistory/TokenDetailsTxHistoryTransactionStateConverter.kt @@ -50,13 +50,13 @@ internal class TokenDetailsTxHistoryTransactionStateConverter( } else { when (type) { is TransactionType.Approve -> R.drawable.ic_doc_24 - is TransactionType.TronStakingTransactionType.Stake, - is TransactionType.TronStakingTransactionType.Vote, + is TransactionType.StakingTransactionType.Stake, + is TransactionType.StakingTransactionType.Vote, -> R.drawable.ic_transaction_history_staking_24 - is TransactionType.TronStakingTransactionType.ClaimRewards, + is TransactionType.StakingTransactionType.ClaimRewards, -> R.drawable.ic_transaction_history_claim_rewards_24 - is TransactionType.TronStakingTransactionType.Unstake, - is TransactionType.TronStakingTransactionType.Withdraw, + is TransactionType.StakingTransactionType.Unstake, + is TransactionType.StakingTransactionType.Withdraw, -> R.drawable.ic_transaction_history_unstaking_24 is TransactionType.Operation, is TransactionType.Swap, @@ -72,11 +72,11 @@ internal class TokenDetailsTxHistoryTransactionStateConverter( is TransactionType.Swap -> resourceReference(R.string.common_swap) is TransactionType.Transfer -> resourceReference(R.string.common_transfer) is TransactionType.UnknownOperation -> resourceReference(R.string.transaction_history_operation) - is TransactionType.TronStakingTransactionType.Stake -> resourceReference(R.string.common_stake) - is TransactionType.TronStakingTransactionType.Unstake -> resourceReference(R.string.common_unstake) - is TransactionType.TronStakingTransactionType.Vote -> resourceReference(R.string.staking_vote) - is TransactionType.TronStakingTransactionType.ClaimRewards -> resourceReference(R.string.common_claim_rewards) - is TransactionType.TronStakingTransactionType.Withdraw -> resourceReference(R.string.staking_withdraw) + is TransactionType.StakingTransactionType.Stake -> resourceReference(R.string.common_stake) + is TransactionType.StakingTransactionType.Unstake -> resourceReference(R.string.common_unstake) + is TransactionType.StakingTransactionType.Vote -> resourceReference(R.string.staking_vote) + is TransactionType.StakingTransactionType.ClaimRewards -> resourceReference(R.string.common_claim_rewards) + is TransactionType.StakingTransactionType.Withdraw -> resourceReference(R.string.staking_withdraw) } private fun TxHistoryItem.extractSubtitle(): TextReference = @@ -119,9 +119,9 @@ internal class TokenDetailsTxHistoryTransactionStateConverter( } private fun TxHistoryItem.getAmount(): String { - if (type is TransactionType.TronStakingTransactionType.Vote || - type == TransactionType.TronStakingTransactionType.ClaimRewards || - type == TransactionType.TronStakingTransactionType.Withdraw + if (type is TransactionType.StakingTransactionType.Vote || + type == TransactionType.StakingTransactionType.ClaimRewards || + type == TransactionType.StakingTransactionType.Withdraw ) { return "" } diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/TxHistoryItemStateConverter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/TxHistoryItemStateConverter.kt index 461f74999f..36ea12af34 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/TxHistoryItemStateConverter.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/TxHistoryItemStateConverter.kt @@ -49,13 +49,13 @@ internal class TxHistoryItemStateConverter( } else { when (type) { is TransactionType.Approve -> R.drawable.ic_doc_24 - is TransactionType.TronStakingTransactionType.Stake, - is TransactionType.TronStakingTransactionType.Vote, + is TransactionType.StakingTransactionType.Stake, + is TransactionType.StakingTransactionType.Vote, -> R.drawable.ic_transaction_history_staking_24 - is TransactionType.TronStakingTransactionType.ClaimRewards, + is TransactionType.StakingTransactionType.ClaimRewards, -> R.drawable.ic_transaction_history_claim_rewards_24 - is TransactionType.TronStakingTransactionType.Unstake, - is TransactionType.TronStakingTransactionType.Withdraw, + is TransactionType.StakingTransactionType.Unstake, + is TransactionType.StakingTransactionType.Withdraw, -> R.drawable.ic_transaction_history_unstaking_24 is TransactionType.Operation, is TransactionType.Swap, @@ -70,11 +70,11 @@ internal class TxHistoryItemStateConverter( is TransactionType.Operation -> stringReference(type.name) is TransactionType.Swap -> resourceReference(R.string.common_swap) is TransactionType.Transfer -> resourceReference(R.string.common_transfer) - is TransactionType.TronStakingTransactionType.Stake -> resourceReference(R.string.common_stake) - is TransactionType.TronStakingTransactionType.Unstake -> resourceReference(R.string.common_unstake) - is TransactionType.TronStakingTransactionType.Vote -> resourceReference(R.string.staking_vote) - is TransactionType.TronStakingTransactionType.ClaimRewards -> resourceReference(R.string.common_claim_rewards) - is TransactionType.TronStakingTransactionType.Withdraw -> { resourceReference(R.string.staking_withdraw) } + is TransactionType.StakingTransactionType.Stake -> resourceReference(R.string.common_stake) + is TransactionType.StakingTransactionType.Unstake -> resourceReference(R.string.common_unstake) + is TransactionType.StakingTransactionType.Vote -> resourceReference(R.string.staking_vote) + is TransactionType.StakingTransactionType.ClaimRewards -> resourceReference(R.string.common_claim_rewards) + is TransactionType.StakingTransactionType.Withdraw -> { resourceReference(R.string.staking_withdraw) } is TransactionType.UnknownOperation -> resourceReference(R.string.transaction_history_operation) } @@ -119,9 +119,9 @@ internal class TxHistoryItemStateConverter( } private fun TxHistoryItem.getAmount(): String { - if (type is TransactionType.TronStakingTransactionType.Vote || - type == TransactionType.TronStakingTransactionType.ClaimRewards || - type == TransactionType.TronStakingTransactionType.Withdraw + if (type is TransactionType.StakingTransactionType.Vote || + type == TransactionType.StakingTransactionType.ClaimRewards || + type == TransactionType.StakingTransactionType.Withdraw ) { return "" } From 90b795635a683d48b1e80e59a5bfd6c13bd677d4 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 4 Nov 2024 13:34:20 +0500 Subject: [PATCH 079/106] Updated on 2026-08-14 --- .../src/main/assets/contract_methods.json | 20 +++++++++++++++++++ .../utils/SdkTransactionTypeConverter.kt | 4 ++++ .../domain/txhistory/models/TxHistoryItem.kt | 1 + ...tailsTxHistoryTransactionStateConverter.kt | 2 ++ .../converter/TxHistoryItemStateConverter.kt | 4 +++- 5 files changed, 30 insertions(+), 1 deletion(-) diff --git a/domain/legacy/src/main/assets/contract_methods.json b/domain/legacy/src/main/assets/contract_methods.json index 6a8d6dafc4..671f0b7d6b 100644 --- a/domain/legacy/src/main/assets/contract_methods.json +++ b/domain/legacy/src/main/assets/contract_methods.json @@ -181,5 +181,25 @@ "info": "stakePOL", "source": "https://etherscan.io/tx/0x96ee64ad31a045982a4776336d852af3c25dc39b6d01bea987aefd460b816773", "name": "withdrawRewardsPOL" + }, + "0x982ef0a7": { + "info": "stakeBSC", + "source": "https://bscscan.com/tx/0xd5ab62ee525f3dc2821de44c31e278f2b49a0202f4e0279e6d0fced05a034595", + "name": "delegate" + }, + "0x4d99dd16": { + "info": "stakeBSC", + "source": "https://bscscan.com/tx/0x866ba857e96051d7cea271723fccde4113c9909dccb534202a2a87ad4c56cb67", + "name": "undelegate" + }, + "0x59491871": { + "info": "stakeBSC", + "source": "https://bscscan.com/tx/0x69615650be1e8a6d94258483f4e880fc6b4d4d9ae53d967c82d847691d91dd71", + "name": "redelegate" + }, + "0xaad3ec96": { + "info": "stakeBSC", + "source": "https://bscscan.com/tx/0xfcbeac6e0a4ba5768d97d14b5115d08dda92defa8a8deee8bba34e2f0655c52b", + "name": "claim" } } diff --git a/domain/legacy/src/main/java/com/tangem/domain/walletmanager/utils/SdkTransactionTypeConverter.kt b/domain/legacy/src/main/java/com/tangem/domain/walletmanager/utils/SdkTransactionTypeConverter.kt index fa74187f5a..4b2fb14d78 100644 --- a/domain/legacy/src/main/java/com/tangem/domain/walletmanager/utils/SdkTransactionTypeConverter.kt +++ b/domain/legacy/src/main/java/com/tangem/domain/walletmanager/utils/SdkTransactionTypeConverter.kt @@ -45,16 +45,20 @@ internal class SdkTransactionTypeConverter( "swap" -> TxHistoryItem.TransactionType.Swap "buyVoucher", "buyVoucherPOL", + "delegate", -> TxHistoryItem.TransactionType.StakingTransactionType.Stake "sellVoucher_new", "sellVoucher_newPOL", + "undelegate", -> TxHistoryItem.TransactionType.StakingTransactionType.Unstake "unstakeClaimTokens_new", "unstakeClaimTokens_newPOL", + "claim", -> TxHistoryItem.TransactionType.StakingTransactionType.Withdraw "withdrawRewards", "withdrawRewardsPOL", -> TxHistoryItem.TransactionType.StakingTransactionType.ClaimRewards + "redelegate" -> TxHistoryItem.TransactionType.StakingTransactionType.Restake null -> TxHistoryItem.TransactionType.UnknownOperation else -> TxHistoryItem.TransactionType.Operation(name = methodName.replaceFirstChar { it.titlecase() }) } diff --git a/domain/txhistory/models/src/main/kotlin/com/tangem/domain/txhistory/models/TxHistoryItem.kt b/domain/txhistory/models/src/main/kotlin/com/tangem/domain/txhistory/models/TxHistoryItem.kt index 526be8774c..f331f6a2a1 100644 --- a/domain/txhistory/models/src/main/kotlin/com/tangem/domain/txhistory/models/TxHistoryItem.kt +++ b/domain/txhistory/models/src/main/kotlin/com/tangem/domain/txhistory/models/TxHistoryItem.kt @@ -46,6 +46,7 @@ data class TxHistoryItem( data object Stake : StakingTransactionType data object Unstake : StakingTransactionType data object Withdraw : StakingTransactionType + data object Restake : StakingTransactionType } } diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/txhistory/TokenDetailsTxHistoryTransactionStateConverter.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/txhistory/TokenDetailsTxHistoryTransactionStateConverter.kt index 91863e54cd..4091224cdc 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/txhistory/TokenDetailsTxHistoryTransactionStateConverter.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/txhistory/TokenDetailsTxHistoryTransactionStateConverter.kt @@ -52,6 +52,7 @@ internal class TokenDetailsTxHistoryTransactionStateConverter( is TransactionType.Approve -> R.drawable.ic_doc_24 is TransactionType.StakingTransactionType.Stake, is TransactionType.StakingTransactionType.Vote, + is TransactionType.StakingTransactionType.Restake, -> R.drawable.ic_transaction_history_staking_24 is TransactionType.StakingTransactionType.ClaimRewards, -> R.drawable.ic_transaction_history_claim_rewards_24 @@ -77,6 +78,7 @@ internal class TokenDetailsTxHistoryTransactionStateConverter( is TransactionType.StakingTransactionType.Vote -> resourceReference(R.string.staking_vote) is TransactionType.StakingTransactionType.ClaimRewards -> resourceReference(R.string.common_claim_rewards) is TransactionType.StakingTransactionType.Withdraw -> resourceReference(R.string.staking_withdraw) + is TransactionType.StakingTransactionType.Restake -> resourceReference(R.string.staking_restake) } private fun TxHistoryItem.extractSubtitle(): TextReference = diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/TxHistoryItemStateConverter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/TxHistoryItemStateConverter.kt index 36ea12af34..10e1cb4f76 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/TxHistoryItemStateConverter.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/TxHistoryItemStateConverter.kt @@ -51,6 +51,7 @@ internal class TxHistoryItemStateConverter( is TransactionType.Approve -> R.drawable.ic_doc_24 is TransactionType.StakingTransactionType.Stake, is TransactionType.StakingTransactionType.Vote, + is TransactionType.StakingTransactionType.Restake, -> R.drawable.ic_transaction_history_staking_24 is TransactionType.StakingTransactionType.ClaimRewards, -> R.drawable.ic_transaction_history_claim_rewards_24 @@ -74,7 +75,8 @@ internal class TxHistoryItemStateConverter( is TransactionType.StakingTransactionType.Unstake -> resourceReference(R.string.common_unstake) is TransactionType.StakingTransactionType.Vote -> resourceReference(R.string.staking_vote) is TransactionType.StakingTransactionType.ClaimRewards -> resourceReference(R.string.common_claim_rewards) - is TransactionType.StakingTransactionType.Withdraw -> { resourceReference(R.string.staking_withdraw) } + is TransactionType.StakingTransactionType.Withdraw -> resourceReference(R.string.staking_withdraw) + is TransactionType.StakingTransactionType.Restake -> resourceReference(R.string.staking_restake) is TransactionType.UnknownOperation -> resourceReference(R.string.transaction_history_operation) } From 2a26369c447306e430cb79be43ef2b87d76ee48d Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 4 Nov 2024 14:08:15 +0500 Subject: [PATCH 080/106] Updated on 2026-08-14 --- app/src/main/assets/tangem-app-config | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/assets/tangem-app-config b/app/src/main/assets/tangem-app-config index 544358e464..adbdabe422 160000 --- a/app/src/main/assets/tangem-app-config +++ b/app/src/main/assets/tangem-app-config @@ -1 +1 @@ -Subproject commit 544358e4640ae25402dd4186e0f244f9509c8906 +Subproject commit adbdabe422b0513640d1750f276298683d342f61 From 970440d8fdf3d8bc72ace77bb4cd74a9a8d2c793 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 4 Nov 2024 14:49:28 +0500 Subject: [PATCH 081/106] Updated on 2026-08-14 --- gradle/dependencies.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/dependencies.toml b/gradle/dependencies.toml index d78017f8a3..00bc1b4f7c 100644 --- a/gradle/dependencies.toml +++ b/gradle/dependencies.toml @@ -88,7 +88,7 @@ markdownComposeView = "0.5.4" # endregion Other libraries # region Tangem -tangemBlockchainSdk = "release-app_5.18-849" +tangemBlockchainSdk = "release-app_5.18-853" #tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds tangemCardSdk = "release-app_5.18-396" #tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^ From ede7143a0396dcec7d940aa04d2c9a27a91d5c1b Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 4 Nov 2024 17:01:38 +0500 Subject: [PATCH 082/106] Updated on 2026-08-14 --- core/res/src/main/res/values-de/strings.xml | 1 + core/res/src/main/res/values-ja/strings.xml | 1 + core/res/src/main/res/values-ru/strings.xml | 4 +- core/res/src/main/res/values/strings.xml | 3 + .../utils/SdkTransactionTypeConverter.kt | 20 +++--- .../domain/txhistory/models/TxHistoryItem.kt | 14 ++--- .../presentation/state/StakingNotification.kt | 7 ++- .../AmountRequirementStateTransformer.kt | 63 +++++++++++-------- .../AddStakingNotificationsTransformer.kt | 1 + .../StakingInfoNotificationsFactory.kt | 28 +++++++++ ...tailsTxHistoryTransactionStateConverter.kt | 30 ++++----- .../converter/TxHistoryItemStateConverter.kt | 30 ++++----- 12 files changed, 126 insertions(+), 76 deletions(-) diff --git a/core/res/src/main/res/values-de/strings.xml b/core/res/src/main/res/values-de/strings.xml index 4313234fb9..198db71cec 100644 --- a/core/res/src/main/res/values-de/strings.xml +++ b/core/res/src/main/res/values-de/strings.xml @@ -774,6 +774,7 @@ Belohnungen Stake gesperrt Mehr staken + Gestakeder Betrag Du setzt %1$s ein und ethältst Deine Belohnung %2$s Zum Entsperren antippen Tippe zum Entsperren oder Abstimmen diff --git a/core/res/src/main/res/values-ja/strings.xml b/core/res/src/main/res/values-ja/strings.xml index 8cdaeffe4a..d64d288ba7 100644 --- a/core/res/src/main/res/values-ja/strings.xml +++ b/core/res/src/main/res/values-ja/strings.xml @@ -764,6 +764,7 @@ 報酬 ステーキングはロックされています もっとステーキングする + ステーキング中の金額 %1$sをステーキングし、報酬%2$sを受け取ります タップしてロック解除 タップしてロック解除もしくは投票 diff --git a/core/res/src/main/res/values-ru/strings.xml b/core/res/src/main/res/values-ru/strings.xml index c600ccc00e..eb0c4ce63d 100644 --- a/core/res/src/main/res/values-ru/strings.xml +++ b/core/res/src/main/res/values-ru/strings.xml @@ -714,6 +714,7 @@ Имя Сумма для стейкинга должна быть не менее %s Согласно правилам сети, сумма стейкинга будет округлена до %1$sTRX. + Сумма для вывода из стейкинга будет округлена до %1$s TRX ввиду особенностей сети. Забрать средства Комиссия за стейкинг аккаунт Стейкинг аккаунт — это специальный счет, на котором хранятся застейканные токены SOL. Он создается при делегировании ваших токенов валидатору для участия в подтверждении транзакций и получении наград. За создание стейкинг аккаунта взимается небольшая комиссия, которая возвращается после завершения стейкинга. @@ -756,6 +757,7 @@ Стейкинг дает возможность вам получать %1$s. Награда будет зачисляться каждую неделю. Стейкайте безопасно и начинайте получать еженедельные награды. Получите награду за стейкинг + Оставшийся застейканный баланс будет слишком мал для вывода. Вам потребуется застейкать больше средств, чтобы достичь минимальной суммы для вывода. Стейкинг временно недоступен из-за проблем в сети. Пожалуйста, попробуйте позже. Стейкинг в сети %1$s с новым валидатором автоматически переведет ваши текущие застейканные средства на него. Реинвестируйте свои заработанные награды в вашу застейканную сумму, увеличивая потенциальный доход @@ -798,7 +800,7 @@ Отзыв Разблокировать Разблокировка - Сумма для стейкинга должна быть не менее %s + Сумма для вывода из стейкинга должна быть не менее %s Сумма превышает застейканный баланс Вывод из стейкинга Завершение стейкинга diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml index 3a507e68ab..10d590ce52 100644 --- a/core/res/src/main/res/values/strings.xml +++ b/core/res/src/main/res/values/strings.xml @@ -741,6 +741,8 @@ Staking allows you to earn %1$s. Your staking rewards arrive every week. Stake securely and start earning weekly rewards Earn staking rewards + Your remaining staked balance will be too low to unstake. You’ll need to stake more to meet the minimum unstake amount. + Low staked balance Staking is currently unavailable due to network conditions. Please try again later. Staking in the %1$s network with a new validator will automatically transfer all previously staked funds to this validator Reinvests your earned rewards in your staked amount, increasing potential earnings. @@ -861,6 +863,7 @@ from: %s to: %s validator: %s + The minimum transaction amount is %1$s. Try again You\'ve scanned the same card. To create a twin wallet you need to scan the card with number %d You\'ve scanned wrong twin card. Please try another one diff --git a/domain/legacy/src/main/java/com/tangem/domain/walletmanager/utils/SdkTransactionTypeConverter.kt b/domain/legacy/src/main/java/com/tangem/domain/walletmanager/utils/SdkTransactionTypeConverter.kt index 4b2fb14d78..bdfee927b3 100644 --- a/domain/legacy/src/main/java/com/tangem/domain/walletmanager/utils/SdkTransactionTypeConverter.kt +++ b/domain/legacy/src/main/java/com/tangem/domain/walletmanager/utils/SdkTransactionTypeConverter.kt @@ -21,19 +21,19 @@ internal class SdkTransactionTypeConverter( TxHistoryItem.TransactionType.Transfer } is TransactionType.TronStakingTransactionType.FreezeBalanceV2Contract -> { - TxHistoryItem.TransactionType.StakingTransactionType.Stake + TxHistoryItem.TransactionType.Staking.Stake } is TransactionType.TronStakingTransactionType.UnfreezeBalanceV2Contract -> { - TxHistoryItem.TransactionType.StakingTransactionType.Unstake + TxHistoryItem.TransactionType.Staking.Unstake } is TransactionType.TronStakingTransactionType.VoteWitnessContract -> { - TxHistoryItem.TransactionType.StakingTransactionType.Vote(value.validatorAddress) + TxHistoryItem.TransactionType.Staking.Vote(value.validatorAddress) } is TransactionType.TronStakingTransactionType.WithdrawBalanceContract -> { - TxHistoryItem.TransactionType.StakingTransactionType.ClaimRewards + TxHistoryItem.TransactionType.Staking.ClaimRewards } is TransactionType.TronStakingTransactionType.WithdrawExpireUnfreezeContract -> { - TxHistoryItem.TransactionType.StakingTransactionType.Withdraw + TxHistoryItem.TransactionType.Staking.Withdraw } } } @@ -46,19 +46,19 @@ internal class SdkTransactionTypeConverter( "buyVoucher", "buyVoucherPOL", "delegate", - -> TxHistoryItem.TransactionType.StakingTransactionType.Stake + -> TxHistoryItem.TransactionType.Staking.Stake "sellVoucher_new", "sellVoucher_newPOL", "undelegate", - -> TxHistoryItem.TransactionType.StakingTransactionType.Unstake + -> TxHistoryItem.TransactionType.Staking.Unstake "unstakeClaimTokens_new", "unstakeClaimTokens_newPOL", "claim", - -> TxHistoryItem.TransactionType.StakingTransactionType.Withdraw + -> TxHistoryItem.TransactionType.Staking.Withdraw "withdrawRewards", "withdrawRewardsPOL", - -> TxHistoryItem.TransactionType.StakingTransactionType.ClaimRewards - "redelegate" -> TxHistoryItem.TransactionType.StakingTransactionType.Restake + -> TxHistoryItem.TransactionType.Staking.ClaimRewards + "redelegate" -> TxHistoryItem.TransactionType.Staking.Restake null -> TxHistoryItem.TransactionType.UnknownOperation else -> TxHistoryItem.TransactionType.Operation(name = methodName.replaceFirstChar { it.titlecase() }) } diff --git a/domain/txhistory/models/src/main/kotlin/com/tangem/domain/txhistory/models/TxHistoryItem.kt b/domain/txhistory/models/src/main/kotlin/com/tangem/domain/txhistory/models/TxHistoryItem.kt index f331f6a2a1..61f2278715 100644 --- a/domain/txhistory/models/src/main/kotlin/com/tangem/domain/txhistory/models/TxHistoryItem.kt +++ b/domain/txhistory/models/src/main/kotlin/com/tangem/domain/txhistory/models/TxHistoryItem.kt @@ -40,13 +40,13 @@ data class TxHistoryItem( data object UnknownOperation : TransactionType data class Operation(val name: String) : TransactionType - sealed interface StakingTransactionType : TransactionType { - data class Vote(val validatorAddress: String) : StakingTransactionType - data object ClaimRewards : StakingTransactionType - data object Stake : StakingTransactionType - data object Unstake : StakingTransactionType - data object Withdraw : StakingTransactionType - data object Restake : StakingTransactionType + sealed interface Staking : TransactionType { + data class Vote(val validatorAddress: String) : Staking + data object ClaimRewards : Staking + data object Stake : Staking + data object Unstake : Staking + data object Withdraw : Staking + data object Restake : Staking } } diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/StakingNotification.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/StakingNotification.kt index 5b02449fa4..d9fd203280 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/StakingNotification.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/StakingNotification.kt @@ -39,7 +39,7 @@ internal object StakingNotification { ) : NotificationUM.Warning( title = title, subtitle = subtitle, - iconResId = R.drawable.ic_alert_circle_24, + iconResId = R.drawable.img_attention_20, buttonsState = buttonsState, onCloseClick = onCloseClick, ) { @@ -47,6 +47,11 @@ internal object StakingNotification { val title: TextReference, val description: TextReference, ) : StakingNotification.Warning(title = title, subtitle = description) + + data object LowStakedBalance : StakingNotification.Warning( + title = resourceReference(R.string.staking_notification_low_staked_balance_title), + subtitle = resourceReference(R.string.staking_notification_low_staked_balance_text), + ) } sealed class Info( diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/amount/AmountRequirementStateTransformer.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/amount/AmountRequirementStateTransformer.kt index 199707ada1..2543480a3f 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/amount/AmountRequirementStateTransformer.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/amount/AmountRequirementStateTransformer.kt @@ -1,5 +1,6 @@ package com.tangem.features.staking.impl.presentation.state.transformers.amount +import androidx.annotation.StringRes import androidx.compose.ui.text.input.ImeAction import com.tangem.common.extensions.isZero import com.tangem.common.ui.amountScreen.models.AmountState @@ -26,25 +27,18 @@ internal class AmountRequirementStateTransformer( private val actionType: StakingActionCommonType, ) : Transformer { override fun transform(prevState: AmountState): AmountState { - val amountRequirements = yield.args.enter.args[Yield.Args.ArgType.AMOUNT] - - return if (prevState is AmountState.Data && amountRequirements != null) { + return if (prevState is AmountState.Data) { updateWithError( prevState, actionType, - amountRequirements, ) } else { prevState } } - private fun updateWithError( - amountState: AmountState.Data, - actionType: StakingActionCommonType, - amountRequirements: AddressArgument, - ): AmountState { - val isRequirementError = isRequirementError(amountState, amountRequirements) + private fun updateWithError(amountState: AmountState.Data, actionType: StakingActionCommonType): AmountState { + val requirementError = getRequirementError(amountState) val isIntegerOnlyError = isIntegerOnlyError(amountState, actionType) val cryptoAmount = amountState.amountTextField.cryptoAmount @@ -53,14 +47,7 @@ internal class AmountRequirementStateTransformer( val errorText = when { amountState.amountTextField.isError -> amountState.amountTextField.error - isRequirementError -> resourceReference( - R.string.staking_amount_requirement_error, - wrappedList( - amountRequirements.minimum.format { - crypto(cryptoCurrencyStatus.currency) - }, - ), - ) + requirementError != null -> requirementError isIntegerOnlyError -> when (actionType) { StakingActionCommonType.Enter -> resourceReference( R.string.staking_amount_tron_integer_error, @@ -70,11 +57,11 @@ internal class AmountRequirementStateTransformer( R.string.staking_amount_tron_integer_error_unstaking, wrappedList(value), ) - else -> TODO() + else -> TextReference.EMPTY } else -> TextReference.EMPTY } - val isError = amountState.amountTextField.isError || isRequirementError + val isError = amountState.amountTextField.isError || requirementError != null return if (!errorText.isNullOrEmpty()) { amountState.copy( isPrimaryButtonEnabled = !isError, @@ -92,17 +79,25 @@ internal class AmountRequirementStateTransformer( } } - private fun isRequirementError(prevState: AmountState.Data, amountRequirements: AddressArgument): Boolean { - val amountDecimal = prevState.amountTextField.cryptoAmount.value ?: return false + private fun getRequirementError(prevState: AmountState.Data): TextReference? { + val amountDecimal = prevState.amountTextField.cryptoAmount.value ?: return null val isAlreadyErrorState = prevState.amountTextField.isError - val isAmountRequired = amountRequirements.required val isAmountZero = amountDecimal.isZero() - val isExceedsRequirements = - amountRequirements.maximum?.compareTo(amountDecimal) == -1 || - amountRequirements.minimum?.compareTo(amountDecimal) == 1 - return !isAmountZero && isAmountRequired && isExceedsRequirements && !isAlreadyErrorState + if (isAlreadyErrorState || isAmountZero) return null + + return when (actionType) { + StakingActionCommonType.Enter -> { + val enterRequirements = yield.args.enter.args[Yield.Args.ArgType.AMOUNT] + enterRequirements?.getError(amountDecimal, R.string.staking_amount_requirement_error) + } + StakingActionCommonType.Exit -> { + val exitRequirements = yield.args.exit?.args?.get(Yield.Args.ArgType.AMOUNT) + exitRequirements?.getError(amountDecimal, R.string.staking_unstake_amount_requirement_error) + } + else -> null + } } private fun isIntegerOnlyError(amountState: AmountState.Data, actionType: StakingActionCommonType): Boolean { @@ -116,6 +111,20 @@ internal class AmountRequirementStateTransformer( return isEnterOrExit && isTron && !isIntegerOnly } + private fun AddressArgument.getError(amount: BigDecimal, @StringRes errorTextRes: Int): TextReference? { + val isExceedsRequirements = maximum?.compareTo(amount) == -1 || + minimum?.compareTo(amount) == 1 + + return resourceReference( + errorTextRes, + wrappedList( + minimum.format { + crypto(cryptoCurrencyStatus.currency) + }, + ), + ).takeIf { required && isExceedsRequirements } + } + data class Data( val amountState: AmountState, val actionType: StakingActionCommonType, diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/notifications/AddStakingNotificationsTransformer.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/notifications/AddStakingNotificationsTransformer.kt index 2f086ac68c..530e3cc24b 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/notifications/AddStakingNotificationsTransformer.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/notifications/AddStakingNotificationsTransformer.kt @@ -107,6 +107,7 @@ internal class AddStakingNotificationsTransformer( notifications = this, prevState = prevState, sendingAmount = sendingAmount, + actionAmount = amountValue, feeValue = feeValue, ) }.toImmutableList() diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/notifications/StakingInfoNotificationsFactory.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/notifications/StakingInfoNotificationsFactory.kt index f610a6b17b..36f7acec1c 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/notifications/StakingInfoNotificationsFactory.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/notifications/StakingInfoNotificationsFactory.kt @@ -17,6 +17,7 @@ import com.tangem.features.staking.impl.presentation.state.StakingUiState import com.tangem.lib.crypto.BlockchainUtils.isCosmos import com.tangem.lib.crypto.BlockchainUtils.isTron import com.tangem.utils.Provider +import com.tangem.utils.extensions.isZero import com.tangem.utils.extensions.orZero import java.math.BigDecimal @@ -26,12 +27,22 @@ internal class StakingInfoNotificationsFactory( private val isSubtractAvailable: Boolean, ) { + /** + * @param notifications current notification to display + * @param prevState current screen state to update + * @param sendingAmount amount being transferred from user account + * @param actionAmount any amount being transferred or used action + * @param feeValue fee amount payed from user account + */ fun addInfoNotifications( notifications: MutableList, prevState: StakingUiState, sendingAmount: BigDecimal, + actionAmount: BigDecimal, feeValue: BigDecimal, ) = with(notifications) { + addStakingLowBalanceNotification(prevState, actionAmount) + when (prevState.actionType) { StakingActionCommonType.Enter -> addEnterInfoNotifications(sendingAmount, feeValue) StakingActionCommonType.Exit -> addExitInfoNotifications() @@ -149,4 +160,21 @@ internal class StakingInfoNotificationsFactory( add(StakingNotification.Info.StakeEntireBalance) } } + + private fun MutableList.addStakingLowBalanceNotification( + prevState: StakingUiState, + actionAmount: BigDecimal, + ) { + if (prevState.actionType != StakingActionCommonType.Exit) return + + val maxAmount = prevState.balanceState?.cryptoAmount ?: return + val exitRequirements = yield.args.exit?.args?.get(Yield.Args.ArgType.AMOUNT) ?: return + + val amountLeft = maxAmount - actionAmount + val isNotEnoughLeft = !amountLeft.isZero() && amountLeft < exitRequirements.minimum + + if (exitRequirements.required && isNotEnoughLeft) { + add(StakingNotification.Warning.LowStakedBalance) + } + } } \ No newline at end of file diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/txhistory/TokenDetailsTxHistoryTransactionStateConverter.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/txhistory/TokenDetailsTxHistoryTransactionStateConverter.kt index 4091224cdc..360126d91d 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/txhistory/TokenDetailsTxHistoryTransactionStateConverter.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/txhistory/TokenDetailsTxHistoryTransactionStateConverter.kt @@ -50,14 +50,14 @@ internal class TokenDetailsTxHistoryTransactionStateConverter( } else { when (type) { is TransactionType.Approve -> R.drawable.ic_doc_24 - is TransactionType.StakingTransactionType.Stake, - is TransactionType.StakingTransactionType.Vote, - is TransactionType.StakingTransactionType.Restake, + is TransactionType.Staking.Stake, + is TransactionType.Staking.Vote, + is TransactionType.Staking.Restake, -> R.drawable.ic_transaction_history_staking_24 - is TransactionType.StakingTransactionType.ClaimRewards, + is TransactionType.Staking.ClaimRewards, -> R.drawable.ic_transaction_history_claim_rewards_24 - is TransactionType.StakingTransactionType.Unstake, - is TransactionType.StakingTransactionType.Withdraw, + is TransactionType.Staking.Unstake, + is TransactionType.Staking.Withdraw, -> R.drawable.ic_transaction_history_unstaking_24 is TransactionType.Operation, is TransactionType.Swap, @@ -73,12 +73,12 @@ internal class TokenDetailsTxHistoryTransactionStateConverter( is TransactionType.Swap -> resourceReference(R.string.common_swap) is TransactionType.Transfer -> resourceReference(R.string.common_transfer) is TransactionType.UnknownOperation -> resourceReference(R.string.transaction_history_operation) - is TransactionType.StakingTransactionType.Stake -> resourceReference(R.string.common_stake) - is TransactionType.StakingTransactionType.Unstake -> resourceReference(R.string.common_unstake) - is TransactionType.StakingTransactionType.Vote -> resourceReference(R.string.staking_vote) - is TransactionType.StakingTransactionType.ClaimRewards -> resourceReference(R.string.common_claim_rewards) - is TransactionType.StakingTransactionType.Withdraw -> resourceReference(R.string.staking_withdraw) - is TransactionType.StakingTransactionType.Restake -> resourceReference(R.string.staking_restake) + is TransactionType.Staking.Stake -> resourceReference(R.string.common_stake) + is TransactionType.Staking.Unstake -> resourceReference(R.string.common_unstake) + is TransactionType.Staking.Vote -> resourceReference(R.string.staking_vote) + is TransactionType.Staking.ClaimRewards -> resourceReference(R.string.common_claim_rewards) + is TransactionType.Staking.Withdraw -> resourceReference(R.string.staking_withdraw) + is TransactionType.Staking.Restake -> resourceReference(R.string.staking_restake) } private fun TxHistoryItem.extractSubtitle(): TextReference = @@ -121,9 +121,9 @@ internal class TokenDetailsTxHistoryTransactionStateConverter( } private fun TxHistoryItem.getAmount(): String { - if (type is TransactionType.StakingTransactionType.Vote || - type == TransactionType.StakingTransactionType.ClaimRewards || - type == TransactionType.StakingTransactionType.Withdraw + if (type is TransactionType.Staking.Vote || + type == TransactionType.Staking.ClaimRewards || + type == TransactionType.Staking.Withdraw ) { return "" } diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/TxHistoryItemStateConverter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/TxHistoryItemStateConverter.kt index 10e1cb4f76..a85ca99eab 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/TxHistoryItemStateConverter.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/TxHistoryItemStateConverter.kt @@ -49,14 +49,14 @@ internal class TxHistoryItemStateConverter( } else { when (type) { is TransactionType.Approve -> R.drawable.ic_doc_24 - is TransactionType.StakingTransactionType.Stake, - is TransactionType.StakingTransactionType.Vote, - is TransactionType.StakingTransactionType.Restake, + is TransactionType.Staking.Stake, + is TransactionType.Staking.Vote, + is TransactionType.Staking.Restake, -> R.drawable.ic_transaction_history_staking_24 - is TransactionType.StakingTransactionType.ClaimRewards, + is TransactionType.Staking.ClaimRewards, -> R.drawable.ic_transaction_history_claim_rewards_24 - is TransactionType.StakingTransactionType.Unstake, - is TransactionType.StakingTransactionType.Withdraw, + is TransactionType.Staking.Unstake, + is TransactionType.Staking.Withdraw, -> R.drawable.ic_transaction_history_unstaking_24 is TransactionType.Operation, is TransactionType.Swap, @@ -71,12 +71,12 @@ internal class TxHistoryItemStateConverter( is TransactionType.Operation -> stringReference(type.name) is TransactionType.Swap -> resourceReference(R.string.common_swap) is TransactionType.Transfer -> resourceReference(R.string.common_transfer) - is TransactionType.StakingTransactionType.Stake -> resourceReference(R.string.common_stake) - is TransactionType.StakingTransactionType.Unstake -> resourceReference(R.string.common_unstake) - is TransactionType.StakingTransactionType.Vote -> resourceReference(R.string.staking_vote) - is TransactionType.StakingTransactionType.ClaimRewards -> resourceReference(R.string.common_claim_rewards) - is TransactionType.StakingTransactionType.Withdraw -> resourceReference(R.string.staking_withdraw) - is TransactionType.StakingTransactionType.Restake -> resourceReference(R.string.staking_restake) + is TransactionType.Staking.Stake -> resourceReference(R.string.common_stake) + is TransactionType.Staking.Unstake -> resourceReference(R.string.common_unstake) + is TransactionType.Staking.Vote -> resourceReference(R.string.staking_vote) + is TransactionType.Staking.ClaimRewards -> resourceReference(R.string.common_claim_rewards) + is TransactionType.Staking.Withdraw -> resourceReference(R.string.staking_withdraw) + is TransactionType.Staking.Restake -> resourceReference(R.string.staking_restake) is TransactionType.UnknownOperation -> resourceReference(R.string.transaction_history_operation) } @@ -121,9 +121,9 @@ internal class TxHistoryItemStateConverter( } private fun TxHistoryItem.getAmount(): String { - if (type is TransactionType.StakingTransactionType.Vote || - type == TransactionType.StakingTransactionType.ClaimRewards || - type == TransactionType.StakingTransactionType.Withdraw + if (type is TransactionType.Staking.Vote || + type == TransactionType.Staking.ClaimRewards || + type == TransactionType.Staking.Withdraw ) { return "" } From c44c0b19a298842b2f4d7e5bbb9f41140df55c54 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 4 Nov 2024 17:19:57 +0300 Subject: [PATCH 083/106] Updated on 2026-08-14 --- gradle/dependencies.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/dependencies.toml b/gradle/dependencies.toml index 00bc1b4f7c..2e813b0545 100644 --- a/gradle/dependencies.toml +++ b/gradle/dependencies.toml @@ -88,7 +88,7 @@ markdownComposeView = "0.5.4" # endregion Other libraries # region Tangem -tangemBlockchainSdk = "release-app_5.18-853" +tangemBlockchainSdk = "release-app_5.18-856" #tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds tangemCardSdk = "release-app_5.18-396" #tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^ From 0a70defb2b1d84a732a5a0ec9803666d85448ba3 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 4 Nov 2024 15:10:19 +0400 Subject: [PATCH 084/106] Updated on 2026-08-14 --- .../feature/wallet/presentation/wallet/ui/WalletScreen.kt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletScreen.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletScreen.kt index 8dc7d7fd8f..c1d3d9cd26 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletScreen.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletScreen.kt @@ -179,7 +179,11 @@ private fun WalletContent( contentPadding = contentPadding, horizontalAlignment = Alignment.CenterHorizontally, ) { - item(key = "WalletsList" + state.selectedWalletIndex, contentType = "WalletsList") { + item( + // !!! Type of the key should be saveable via Bundle on Android !!! + key = state.wallets.map { it.walletCardState.id.stringValue }, + contentType = state.wallets.map { it.walletCardState.id }, + ) { WalletsList( lazyListState = walletsListState, wallets = state.wallets.map(WalletState::walletCardState).toImmutableList(), From d80dbf1d5b37d21664686697f7e95ddc0eb13a7d Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 5 Nov 2024 11:44:22 +0500 Subject: [PATCH 085/106] Updated on 2026-08-14 --- .../presentation/state/StakingStateController.kt | 2 +- .../impl/presentation/state/StakingStateRouter.kt | 13 +++++++++---- .../impl/presentation/state/StakingUiState.kt | 2 +- .../transformers/SetInitialDataStateTransformer.kt | 2 +- .../presentation/ui/StakingConfirmationContent.kt | 9 ++++++--- .../staking/impl/presentation/ui/StakingScreen.kt | 2 ++ 6 files changed, 20 insertions(+), 10 deletions(-) diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/StakingStateController.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/StakingStateController.kt index 71d3c67900..4150cef942 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/StakingStateController.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/StakingStateController.kt @@ -75,7 +75,7 @@ internal class StakingStateController @Inject constructor( walletName = "", cryptoCurrencyName = "", cryptoCurrencySymbol = "", - cryptoCurrencyNetworkId = "", + cryptoCurrencyBlockchainId = "", currentStep = StakingStep.InitialInfo, initialInfoState = StakingStates.InitialInfoState.Empty(), amountState = AmountState.Empty(), diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/StakingStateRouter.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/StakingStateRouter.kt index 48823d364f..d7e8d24c25 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/StakingStateRouter.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/StakingStateRouter.kt @@ -26,7 +26,7 @@ internal class StakingStateRouter( StakingStep.InitialInfo -> when (stateController.value.actionType) { StakingActionCommonType.Enter -> showAmount() // TODO staking [REDACTED_TASK_KEY] support solana multisize hashes signing - StakingActionCommonType.Exit -> if (isSolana(stateController.value.cryptoCurrencyNetworkId)) { + StakingActionCommonType.Exit -> if (isSolana(stateController.value.cryptoCurrencyBlockchainId)) { showConfirmation() } else { showAmount() @@ -54,10 +54,15 @@ internal class StakingStateRouter( StakingStep.Amount, -> showInitial() StakingStep.Confirmation -> { - if (uiState.actionType != StakingActionCommonType.Enter) { - showInitial() - } else { + val isEnter = uiState.actionType == StakingActionCommonType.Enter + val isExit = uiState.actionType == StakingActionCommonType.Exit + + // TODO staking [REDACTED_TASK_KEY] support solana multisize hashes signing + val isSolana = isSolana(uiState.cryptoCurrencyBlockchainId) + if (isEnter || isExit && !isSolana) { showAmount() + } else { + showInitial() } } StakingStep.Validators -> showConfirmation() diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/StakingUiState.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/StakingUiState.kt index 470048621d..c8c0be08e6 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/StakingUiState.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/StakingUiState.kt @@ -29,7 +29,7 @@ internal data class StakingUiState( val walletName: String, val cryptoCurrencyName: String, val cryptoCurrencySymbol: String, - val cryptoCurrencyNetworkId: String, + val cryptoCurrencyBlockchainId: String, val currentStep: StakingStep, val initialInfoState: StakingStates.InitialInfoState, val amountState: AmountState, diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetInitialDataStateTransformer.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetInitialDataStateTransformer.kt index e4cc55d35c..7a17d070d9 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetInitialDataStateTransformer.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetInitialDataStateTransformer.kt @@ -75,7 +75,7 @@ internal class SetInitialDataStateTransformer( title = TextReference.EMPTY, cryptoCurrencyName = cryptoCurrency.name, cryptoCurrencySymbol = cryptoCurrency.symbol, - cryptoCurrencyNetworkId = cryptoCurrency.network.id.value, + cryptoCurrencyBlockchainId = cryptoCurrency.network.id.value, clickIntents = clickIntents, currentStep = StakingStep.InitialInfo, initialInfoState = createInitialInfoState(), diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingConfirmationContent.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingConfirmationContent.kt index ad11c31650..2df8cdd525 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingConfirmationContent.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingConfirmationContent.kt @@ -32,6 +32,7 @@ import com.tangem.features.staking.impl.presentation.ui.block.StakingFeeBlock import com.tangem.features.staking.impl.presentation.ui.block.ValidatorBlock import com.tangem.features.staking.impl.presentation.viewmodel.StakingClickIntents +@Suppress("LongParameterList") @Composable internal fun StakingConfirmationContent( amountState: AmountState, @@ -39,9 +40,10 @@ internal fun StakingConfirmationContent( validatorState: StakingStates.ValidatorState, clickIntents: StakingClickIntents, type: StakingActionCommonType, + isSolana: Boolean, // TODO staking [REDACTED_TASK_KEY] support solana multisize hashes signing ) { if (state !is StakingStates.ConfirmationState.Data) return - val isEnterAction = type == StakingActionCommonType.Enter + val isAmountEditable = type == StakingActionCommonType.Enter || type == StakingActionCommonType.Exit && !isSolana val isTransactionSent = state.innerState == InnerConfirmationStakingState.COMPLETED val isTransactionInProgress = state.notifications.any { it is StakingNotification.Warning.TransactionInProgress } Column( @@ -63,8 +65,8 @@ internal fun StakingConfirmationContent( } AmountBlock( amountState = amountState, - isClickDisabled = !isEnterAction || isTransactionSent || isTransactionInProgress, - isEditingDisabled = !isEnterAction && state.innerState != InnerConfirmationStakingState.COMPLETED, + isClickDisabled = !isAmountEditable || isTransactionSent || isTransactionInProgress, + isEditingDisabled = !isAmountEditable && state.innerState != InnerConfirmationStakingState.COMPLETED, onClick = clickIntents::onPrevClick, ) ValidatorBlock( @@ -89,6 +91,7 @@ private fun Preview_StakingConfirmationContent() { validatorState = ValidatorStatePreviewData.validatorState, clickIntents = StakingClickIntentsStub, type = StakingActionCommonType.Enter, + isSolana = false, ) } } diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingScreen.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingScreen.kt index 21be700364..8e6b03d04c 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingScreen.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingScreen.kt @@ -28,6 +28,7 @@ import com.tangem.features.staking.impl.presentation.state.bottomsheet.StakingAc import com.tangem.features.staking.impl.presentation.state.bottomsheet.StakingInfoBottomSheetConfig import com.tangem.features.staking.impl.presentation.ui.bottomsheet.StakingActionSelectorBottomSheet import com.tangem.features.staking.impl.presentation.ui.bottomsheet.StakingInfoBottomSheet +import com.tangem.lib.crypto.BlockchainUtils.isSolana import kotlinx.coroutines.delay import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.map @@ -170,6 +171,7 @@ private fun StakingScreenContent(uiState: StakingUiState, modifier: Modifier = M validatorState = uiState.validatorState, clickIntents = uiState.clickIntents, type = uiState.actionType, + isSolana = isSolana(uiState.cryptoCurrencyBlockchainId), ) StakingStep.RestakeValidator, StakingStep.Validators, From 9544abbccefe53d07cd088866ef363706e8bc111 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 5 Nov 2024 11:45:18 +0500 Subject: [PATCH 086/106] Updated on 2026-08-14 --- .../AmountRequirementStateTransformer.kt | 29 ++++++++----------- 1 file changed, 12 insertions(+), 17 deletions(-) diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/amount/AmountRequirementStateTransformer.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/amount/AmountRequirementStateTransformer.kt index 2543480a3f..cb0b0e31e9 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/amount/AmountRequirementStateTransformer.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/amount/AmountRequirementStateTransformer.kt @@ -5,7 +5,6 @@ import androidx.compose.ui.text.input.ImeAction import com.tangem.common.extensions.isZero import com.tangem.common.ui.amountScreen.models.AmountState import com.tangem.core.ui.extensions.TextReference -import com.tangem.core.ui.extensions.isNullOrEmpty import com.tangem.core.ui.extensions.resourceReference import com.tangem.core.ui.extensions.wrappedList import com.tangem.core.ui.format.bigdecimal.crypto @@ -57,26 +56,22 @@ internal class AmountRequirementStateTransformer( R.string.staking_amount_tron_integer_error_unstaking, wrappedList(value), ) - else -> TextReference.EMPTY + else -> null } - else -> TextReference.EMPTY + else -> null } val isError = amountState.amountTextField.isError || requirementError != null - return if (!errorText.isNullOrEmpty()) { - amountState.copy( - isPrimaryButtonEnabled = !isError, - amountTextField = amountState.amountTextField.copy( - isError = isError, - isWarning = isIntegerOnlyError, - error = errorText, - keyboardOptions = amountState.amountTextField.keyboardOptions.copy( - imeAction = ImeAction.None, - ), + return amountState.copy( + isPrimaryButtonEnabled = !isError, + amountTextField = amountState.amountTextField.copy( + isError = isError, + isWarning = isIntegerOnlyError, + error = errorText ?: amountState.amountTextField.error, + keyboardOptions = amountState.amountTextField.keyboardOptions.copy( + imeAction = ImeAction.None, ), - ) - } else { - amountState - } + ), + ) } private fun getRequirementError(prevState: AmountState.Data): TextReference? { From fc70ce1462d1b4bf9d47088ee33e72ef77d0639f Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 5 Nov 2024 14:31:53 +0500 Subject: [PATCH 087/106] Updated on 2026-08-14 --- .../converters/AmountStateConverter.kt | 9 +++--- .../presentation/state/SendStateFactory.kt | 2 +- .../transformers/SetAmountDataTransformer.kt | 32 +++++++++++-------- .../SetInitialDataStateTransformer.kt | 25 ++++++++------- .../viewmodel/StakingViewModel.kt | 23 ------------- 5 files changed, 37 insertions(+), 54 deletions(-) diff --git a/common/ui/src/main/java/com/tangem/common/ui/amountScreen/converters/AmountStateConverter.kt b/common/ui/src/main/java/com/tangem/common/ui/amountScreen/converters/AmountStateConverter.kt index e2adb46d11..d463a635bc 100644 --- a/common/ui/src/main/java/com/tangem/common/ui/amountScreen/converters/AmountStateConverter.kt +++ b/common/ui/src/main/java/com/tangem/common/ui/amountScreen/converters/AmountStateConverter.kt @@ -12,8 +12,8 @@ import com.tangem.core.ui.extensions.resourceReference import com.tangem.core.ui.extensions.stringReference import com.tangem.core.ui.extensions.wrappedList import com.tangem.core.ui.format.bigdecimal.crypto +import com.tangem.core.ui.format.bigdecimal.fiat import com.tangem.core.ui.format.bigdecimal.format -import com.tangem.core.ui.utils.BigDecimalFormatter.formatFiatAmount import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.utils.Provider @@ -26,7 +26,7 @@ import kotlinx.collections.immutable.persistentListOf * * @property clickIntents amount screen clicks * @property appCurrencyProvider selected app currency provider - * @property maxEnterAmountProvider max enter amount data provider + * @property maxEnterAmount max enter amount data * @property cryptoCurrencyStatusProvider current cryptocurrency status provider * @property iconStateConverter currency icon converter */ @@ -34,7 +34,7 @@ class AmountStateConverter( private val clickIntents: AmountScreenClickIntents, private val appCurrencyProvider: Provider, private val cryptoCurrencyStatusProvider: Provider, - private val maxEnterAmountProvider: Provider, + private val maxEnterAmount: MaxEnterAmount, private val iconStateConverter: CryptoCurrencyToIconStateConverter, ) : Converter { @@ -47,10 +47,9 @@ class AmountStateConverter( } override fun convert(value: AmountParameters): AmountState { - val maxEnterAmount = maxEnterAmountProvider() val appCurrency = appCurrencyProvider() val status = cryptoCurrencyStatusProvider() - val fiat = formatFiatAmount(maxEnterAmount.fiatAmount, appCurrency.code, appCurrency.symbol) + val fiat = maxEnterAmount.fiatAmount.format { fiat(appCurrency.code, appCurrency.symbol) } val crypto = maxEnterAmount.amount.format { crypto(status.currency) } val noFeeRate = status.value.fiatRate.isNullOrZero() diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendStateFactory.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendStateFactory.kt index 0b69329371..030e8e81c7 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendStateFactory.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendStateFactory.kt @@ -44,7 +44,7 @@ internal class SendStateFactory( appCurrencyProvider = appCurrencyProvider, iconStateConverter = iconStateConverter, cryptoCurrencyStatusProvider = cryptoCurrencyStatusProvider, - maxEnterAmountProvider = Provider { maxEnterAmountConverter.convert(cryptoCurrencyStatusProvider()) }, + maxEnterAmount = maxEnterAmountConverter.convert(cryptoCurrencyStatusProvider()), ) } private val recipientStateConverter by lazy(LazyThreadSafetyMode.NONE) { diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetAmountDataTransformer.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetAmountDataTransformer.kt index 434b40908c..b9226a4214 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetAmountDataTransformer.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetAmountDataTransformer.kt @@ -11,7 +11,7 @@ import com.tangem.domain.staking.model.stakekit.action.StakingActionCommonType import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.domain.wallets.models.UserWallet import com.tangem.features.staking.impl.R -import com.tangem.features.staking.impl.presentation.state.* +import com.tangem.features.staking.impl.presentation.state.StakingUiState import com.tangem.features.staking.impl.presentation.viewmodel.StakingClickIntents import com.tangem.utils.Provider import com.tangem.utils.transformer.Transformer @@ -21,30 +21,36 @@ internal class SetAmountDataTransformer( private val cryptoCurrencyStatusProvider: Provider, private val userWalletProvider: Provider, private val appCurrencyProvider: Provider, - private val maxEnterAmountProvider: Provider, ) : Transformer { private val iconStateConverter by lazy(::CryptoCurrencyToIconStateConverter) - private val amountStateConverter by lazy(LazyThreadSafetyMode.NONE) { - AmountStateConverter( - clickIntents = clickIntents, - cryptoCurrencyStatusProvider = cryptoCurrencyStatusProvider, - appCurrencyProvider = appCurrencyProvider, - iconStateConverter = iconStateConverter, - maxEnterAmountProvider = maxEnterAmountProvider, - ) - } - override fun transform(prevState: StakingUiState): StakingUiState { val title = if (prevState.actionType == StakingActionCommonType.Exit) { resourceReference(R.string.staking_staked_amount) } else { stringReference(userWalletProvider().name) } + val cryptoBalanceValue = cryptoCurrencyStatusProvider().value + val (amount, fiatAmount) = if (prevState.actionType != StakingActionCommonType.Enter) { + prevState.balanceState?.cryptoAmount to prevState.balanceState?.fiatAmount + } else { + cryptoBalanceValue.amount to cryptoBalanceValue.fiatAmount + } + val maxEnterAmount = MaxEnterAmount( + amount = amount, + fiatAmount = fiatAmount, + fiatRate = cryptoBalanceValue.fiatRate, + ) return prevState.copy( - amountState = amountStateConverter.convert( + amountState = AmountStateConverter( + clickIntents = clickIntents, + cryptoCurrencyStatusProvider = cryptoCurrencyStatusProvider, + appCurrencyProvider = appCurrencyProvider, + iconStateConverter = iconStateConverter, + maxEnterAmount = maxEnterAmount, + ).convert( AmountParameters( title = title, value = "", diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetInitialDataStateTransformer.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetInitialDataStateTransformer.kt index 7a17d070d9..ef2d4058db 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetInitialDataStateTransformer.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetInitialDataStateTransformer.kt @@ -41,21 +41,10 @@ internal class SetInitialDataStateTransformer( private val userWalletProvider: Provider, private val appCurrencyProvider: Provider, private val balancesToShowProvider: Provider>, - private val maxEnterAmountProvider: Provider, ) : Transformer { private val iconStateConverter by lazy(::CryptoCurrencyToIconStateConverter) - private val amountStateConverter by lazy(LazyThreadSafetyMode.NONE) { - AmountStateConverter( - clickIntents = clickIntents, - cryptoCurrencyStatusProvider = cryptoCurrencyStatusProvider, - appCurrencyProvider = appCurrencyProvider, - iconStateConverter = iconStateConverter, - maxEnterAmountProvider = maxEnterAmountProvider, - ) - } - private val rewardsValidatorStateConverter by lazy(LazyThreadSafetyMode.NONE) { RewardsValidatorStateConverter(cryptoCurrencyStatusProvider, appCurrencyProvider, yield) } @@ -211,7 +200,19 @@ internal class SetInitialDataStateTransformer( } private fun createInitialAmountState(): AmountState { - return amountStateConverter.convert( + val cryptoBalanceValue = cryptoCurrencyStatusProvider().value + val maxEnterAmount = MaxEnterAmount( + amount = cryptoBalanceValue.amount, + fiatAmount = cryptoBalanceValue.fiatAmount, + fiatRate = cryptoBalanceValue.fiatRate, + ) + return AmountStateConverter( + clickIntents = clickIntents, + cryptoCurrencyStatusProvider = cryptoCurrencyStatusProvider, + appCurrencyProvider = appCurrencyProvider, + iconStateConverter = iconStateConverter, + maxEnterAmount = maxEnterAmount, + ).convert( AmountParameters( title = stringReference(userWalletProvider().name), value = "", diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/viewmodel/StakingViewModel.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/viewmodel/StakingViewModel.kt index a15947fe0e..f8dff3cf4a 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/viewmodel/StakingViewModel.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/viewmodel/StakingViewModel.kt @@ -11,7 +11,6 @@ import com.tangem.common.routing.AppRoute import com.tangem.common.routing.bundle.unbundle import com.tangem.common.ui.amountScreen.converters.AmountReduceByTransformer import com.tangem.common.ui.amountScreen.models.AmountState -import com.tangem.common.ui.amountScreen.models.MaxEnterAmount import com.tangem.common.ui.bottomsheet.permission.state.ApproveType import com.tangem.common.ui.bottomsheet.permission.state.GiveTxPermissionBottomSheetConfig import com.tangem.common.ui.notifications.NotificationUM @@ -154,14 +153,6 @@ internal class StakingViewModel @Inject constructor( ).getOrElse { emptyList() } } - private val maxEnterAmount: MaxEnterAmount - get() = - MaxEnterAmount( - amount = uiState.value.balanceState?.cryptoAmount, - fiatAmount = uiState.value.balanceState?.fiatAmount, - fiatRate = cryptoCurrencyStatus.value.fiatRate, - ) - private var isInitialInfoAnalyticSent: Boolean = false private val balanceUpdater by lazy(LazyThreadSafetyMode.NONE) { @@ -233,18 +224,6 @@ internal class StakingViewModel @Inject constructor( } override fun onNextClick(balanceState: BalanceState?) { - if (value.currentStep == StakingStep.InitialInfo && balanceState == null) { - stateController.update( - SetConfirmationStateInitTransformer( - isEnter = true, - isExplicitExit = false, - balanceState = null, - cryptoCurrencyStatus = cryptoCurrencyStatus, - stakingApproval = stakingApproval, - stakingAllowance = stakingAllowance, - ), - ) - } stakingStateRouter.onNextClick() } @@ -913,7 +892,6 @@ internal class StakingViewModel @Inject constructor( userWalletProvider = Provider { userWallet }, appCurrencyProvider = Provider { appCurrency }, balancesToShowProvider = Provider { balancesToShow }, - maxEnterAmountProvider = Provider { maxEnterAmount }, ), SetConfirmationStateEmptyTransformer, ) @@ -947,7 +925,6 @@ internal class StakingViewModel @Inject constructor( cryptoCurrencyStatusProvider = Provider { cryptoCurrencyStatus }, userWalletProvider = Provider { userWallet }, appCurrencyProvider = Provider { appCurrency }, - maxEnterAmountProvider = Provider { maxEnterAmount }, ), AmountChangeStateTransformer( cryptoCurrencyStatus = cryptoCurrencyStatus, From 2e984fce4c8a57b8f324152dbc40b0819a9a105f Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 5 Nov 2024 14:32:24 +0500 Subject: [PATCH 088/106] Updated on 2026-08-14 --- core/res/src/main/res/values-de/strings.xml | 2 ++ core/res/src/main/res/values-es/strings.xml | 3 ++ core/res/src/main/res/values-fr/strings.xml | 3 ++ core/res/src/main/res/values-ja/strings.xml | 2 ++ core/res/src/main/res/values-ru/strings.xml | 3 ++ .../src/main/res/values-uk-rUA/strings.xml | 2 ++ core/res/src/main/res/values/strings.xml | 8 +++++ .../tangem/feature/swap/ui/StateBuilder.kt | 32 ++++++++++++------- 8 files changed, 44 insertions(+), 11 deletions(-) diff --git a/core/res/src/main/res/values-de/strings.xml b/core/res/src/main/res/values-de/strings.xml index 198db71cec..ac5448db47 100644 --- a/core/res/src/main/res/values-de/strings.xml +++ b/core/res/src/main/res/values-de/strings.xml @@ -812,7 +812,9 @@ Tausche mehr Token zu besseren Kursen direkt in deiner Brieftasche. Neuer Swap-Anbieter verfügbar! Der Betrag umfasst:\n- Gebühr des Dienstanbieters\n- Netzgebühr für die Rücksendung von %s von der Vermittlungsstelle an die Adresse des Nutzers. + Der Betrag beinhaltet:\n• Honorar des Dienstleisters\n• Netzwerkgebühr für das Senden %1$s von der Börse zurück an die Adresse des Benutzers. \n\n Provider-Slippage kann bis zu %2$s Der Betrag enthält die Gebühren des Dienstleisters. + Der Betrag beinhaltet die Gebühr des Dienstleisters. \n\nProvider-Slippage kann bis zu %s Anbieter-Slippage kann bis zu %s ausfallen. Gebühren Alle dezentralen Börsen benötigen Genehmigungen, um zu verhindern, dass intelligente Verträge ohne Ihre Erlaubnis auf Ihre Geldbörse zugreifen. Smart Contracts können nicht auf Ihre Token zugreifen, wenn Sie nicht zustimmen. Indem Sie Ihre Token \"freischalten\", ermächtigen Sie den 1-Zoll-Smart-Contract, sie auszugeben. Die Miner des Netzwerks erhalten eine (von Ihnen bezahlte) Gasgebühr, um diese Aktion in der Blockchain aufzuzeichnen. Sie können Ihre Token tauschen, nachdem Sie Ihre Zustimmung gegeben haben. diff --git a/core/res/src/main/res/values-es/strings.xml b/core/res/src/main/res/values-es/strings.xml index a8fc4365c8..ac89c7802b 100644 --- a/core/res/src/main/res/values-es/strings.xml +++ b/core/res/src/main/res/values-es/strings.xml @@ -808,7 +808,9 @@ Intercambie más tokens a mejores tasas directamente en su billetera. ¡Nuevo proveedor de intercambio disponible! El monto incluye:\n• Tarifas del proveedor de servicios\n• Tarifas de red por enviar %s desde el intercambio a la dirección del usuario. + El monto incluye:\n• tarifas del proveedor de servicios\n• tarifas de red por enviar %1$s desde el intercambio a la dirección del usuario. \n\nEl slippage del proveedor puede alcanzar el %2$s El importe incluye los honorarios del proveedor de servicios. + El importe incluye las tarifas del proveedor de servicios. \n\nEl slippage del proveedor puede alcanzar el %s Tarifa Todos los exchanges descentralizados requieren aprobaciones para evitar que los smart contracts accedan a su billetera sin su permiso. Por diseño, los smart contracts no pueden acceder a tus tokens a menos que lo apruebes. Al \"desbloquear\" sus tokens, autoriza al smart contract de 1-inch a gastarlos. Los mineros de la red reciben una tarifa de gas (pagada por voz) para registrar esta acción en la blockchain. Puede intercambiar su token después de dar la aprobación. Aprobar @@ -856,6 +858,7 @@ desde: %s a: %s validador: %s + El monto mínimo para realizar esta transacción es %1$s. Inténtelo de nuevo Ha escaneado la misma tarjeta. Para crear una billetera gemela, necesite escanear la tarjeta con no. %d Ha escaneado la tarjeta gemela incorrecta. Por favor, intente con otra diff --git a/core/res/src/main/res/values-fr/strings.xml b/core/res/src/main/res/values-fr/strings.xml index 42eb2abac8..d2a3505ef9 100644 --- a/core/res/src/main/res/values-fr/strings.xml +++ b/core/res/src/main/res/values-fr/strings.xml @@ -808,7 +808,9 @@ Échangez plus de jetons à de meilleurs taux directement dans votre portefeuille. Nouveau fournisseur d\'échange disponible ! Le montant comprend :\n• les frais du fournisseur de services\n• les frais de réseau pour l\'envoi de %s depuis l\'échange vers l\'adresse de l\'utilisateur. + Le montant comprend :\n• les frais du fournisseur de services\n• les frais de réseau pour l\'envoi de %1$s depuis l\'échange vers l\'adresse de l\'utilisateur. \n\nLe slippage du fournisseur peut atteindre %2$s Le montant comprend les frais du fournisseur de services. + Le montant comprend les frais du fournisseur de services. \n\nLe slippage du fournisseur peut atteindre %s Frais Tous les échanges décentralisés nécessitent des approbations pour empêcher les smart contracts d\'accéder à votre portefeuille sans votre permission. Par conception, les smart contracts ne peuvent pas accéder à vos jetons sans votre approbation. En « déverrouillant » vos jetons, vous autorisez le smart contract 1-inch à les dépenser. Les mineurs du réseau reçoivent des frais de gaz (payés par vous) pour enregistrer cette action sur la blockchain. Vous pouvez échanger votre jeton après avoir donné votre approbation. Approuver @@ -856,6 +858,7 @@ de : %s à : %s validateur : %s + Le montant minimum pour effectuer cette transaction est %1$s. Réessayez Vous avez scanné la même carte. Pour créer un portefeuille jumeau, vous devez scanner la carte portant le numéro %d Vous avez scanné une mauvaise carte jumelle. S\'il vous plaît, essayez-en un autre diff --git a/core/res/src/main/res/values-ja/strings.xml b/core/res/src/main/res/values-ja/strings.xml index d64d288ba7..b2998a24d0 100644 --- a/core/res/src/main/res/values-ja/strings.xml +++ b/core/res/src/main/res/values-ja/strings.xml @@ -802,7 +802,9 @@ より多くのトークンをより良いレートで、ウォレット内にて直接交換します。 新しいスワッププロバイダーが利用可能になりました! この金額には以下が含まれます:\n- サービスプロバイダーの手数料\n- 取引所からユーザーのアドレスに%s を送り返すためのネットワーク手数料。 + 金額には以下が含まれます: \n • サービス プロバイダーの手数料\n • 取引所からユーザーのアドレスに%1$sを送金するためのネットワーク手数料。 \n\nプロバイダーのスリッページは最大%2$sです この金額には、サービスプロバイダーの手数料が含まれています。 + 金額にはサービスプロバイダーの手数料が含まれます。 \n\nプロバイダーのスリッページは最大%s です プロバイダーのスリッページは最大%sです。 手数料 すべての分散型取引所は、スマートコントラクトがあなたの許可なくウォレットにアクセスするのを防ぐために承認を必要とします。設計上、スマートコントラクトは承認なしでトークンにアクセスできません。トークンを「ロック解除」することで、あなたは1-inchのスマートコントラクトがトークンを使うことを承認します。ネットワークのマイナーは、このアクションをブロックチェーンに記録するためのガス料金(あなたが支払う)を受け取ります。承認後、トークンを交換することができます。 diff --git a/core/res/src/main/res/values-ru/strings.xml b/core/res/src/main/res/values-ru/strings.xml index eb0c4ce63d..e73b23b485 100644 --- a/core/res/src/main/res/values-ru/strings.xml +++ b/core/res/src/main/res/values-ru/strings.xml @@ -827,7 +827,9 @@ Обменивайте больше токенов по лучшим курсам прямо в вашем кошельке. Новый провайдер обмена! В сумму включено: \n• комиссия провайдера сервиса\n• комиссия сети за отправку %s от биржи обратно на адрес пользователя. + В сумму включено: \n• комиссия провайдера сервиса\n• комиссия сети за отправку %1$s от биржи обратно на адрес пользователя \n\nПроскальзывание провайдера составляет до %2$s В сумму включена комиссия провайдера сервиса. + В сумму включена комиссия провайдера сервиса. \n\nПроскальзывание провайдера составляет до %s Проскальзывание провайдера составляет до %s. Информация Подтверждения считаются отраслевым стандартом для всех децентрализованных бирж и защищают ваш кошелек от доступа со стороны смарт-контракта без вашего разрешения. По замыслу смарт-контракты не могут получить доступ к вашим токенам, если вы не одобрите доступ со своей стороны. «Разблокируя» свои токены, вы даете смарт-контракту 1inch разрешение тратить ваши активы. Майнеры сети получают компенсацию за газ (оплачиваемый вами) за запись этого действия в блокчейне. Как только разрешение будет предоставлено, вы сможете обменять свой токен. @@ -876,6 +878,7 @@ от: %s на: %s валидатор: %s + Минимальная сумма транзакции равна %1$s. Попробовать снова Вы отсканировали ту же карту. Для создания twin-кошелька вам необходимо отсканировать карту с номером %d Вы отсканировали не ту twin-карту. Пожалуйста, попробуйте отсканировать другую diff --git a/core/res/src/main/res/values-uk-rUA/strings.xml b/core/res/src/main/res/values-uk-rUA/strings.xml index 18f9697aaa..2697aa11d3 100644 --- a/core/res/src/main/res/values-uk-rUA/strings.xml +++ b/core/res/src/main/res/values-uk-rUA/strings.xml @@ -827,7 +827,9 @@ Обмінюйте більше токенів за вигіднішим курсом прямо у своєму гаманці. З\'явився новий провайдер обмінів! Сума включає: \n• комісію постачальника послуг\n• комісію мережі за відправлення %s з біржі назад на адресу користувача. + У суму входить:\n- комісія провайдера\n- мережева комісія за відправку %1$s з біржі назад на адресу користувача. \n\nПроскакування провайдера становить до %2$s Сума включає комісію постачальника послуг. + Сума включає комісію провайдера послуг. \n\nПроскакування провайдера до %s Комісії Всі децентралізовані біржі вимагають схвалення, щоб запобігти доступу смарт-контрактів до вашого гаманця без вашого дозволу. За задумом смарт-контракти не можуть отримати доступ до ваших токенів без вашого схвалення. \"Розблоковуючи\" свої токени, ви дозволяєте смарт-контракту 1inch витрачати ваші активи. Майнери мережі отримують плату за газ (сплачену вами), щоб зафіксувати цю дію в блокчейні. Ви можете обміняти свій токен після того, як дасте дозвіл. Підтвердити diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml index 10d590ce52..c2f7e4c756 100644 --- a/core/res/src/main/res/values/strings.xml +++ b/core/res/src/main/res/values/strings.xml @@ -551,10 +551,16 @@ Access code restore Identical cards Access code + Search by country + Unavailable + Search by currency Pay with Our services are not available in this country Change or confirm it Your residence has been identified as + Residence + Please select the correct country to ensure accurate payment options and services. + Settings Via Group By balance @@ -814,7 +820,9 @@ Exchange more tokens at better rates directly in your wallet. New Swap Provider Available! The amount includes:\n• service provider\'s fee\n• network fee for sending %s from the exchange back to the user\'s address. + The amount includes:\n• service provider\'s fee\n• network fee for sending %1$s from the exchange back to the user\'s address. \n\nProvider slippage is up to %2$s The amount includes the service provider\'s fee. + The amount includes the service provider\'s fee. \n\nProvider slippage is up to %s Provider slippage is up to %s. Information All decentralized exchanges require approvals to prevent smart contracts from accessing your wallet without your permission. By design, smart contracts can\'t access your tokens unless you approve. By \"unlocking\" your tokens, you authorize the 1-inch smart contract to spend them. The network\'s miners receive a gas fee (paid by you) to record this action on the blockchain. You can swap your token after giving approval. diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt index c270a8d837..d5f2d4d92b 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt @@ -1094,10 +1094,20 @@ internal class StateBuilder( provider: SwapProvider, onDismiss: () -> Unit, ): SwapStateHolder { + val slippage = provider.slippage?.let { "${it.parseBigDecimal(1)}$PERCENT" } val combinedMessage = buildList { when (provider.type) { ExchangeProviderType.CEX -> { - add(resourceReference(R.string.swapping_alert_cex_description, wrappedList(token))) + if (slippage != null) { + add( + resourceReference( + id = R.string.swapping_alert_cex_description_with_slippage, + formatArgs = wrappedList(token, slippage), + ), + ) + } else { + add(resourceReference(R.string.swapping_alert_cex_description, wrappedList(token))) + } } ExchangeProviderType.DEX, ExchangeProviderType.DEX_BRIDGE, @@ -1106,18 +1116,18 @@ internal class StateBuilder( add(resourceReference(R.string.swapping_high_price_impact_description)) add(stringReference("\n\n")) } - add(resourceReference(R.string.swapping_alert_dex_description)) + if (slippage != null) { + add( + resourceReference( + id = R.string.swapping_alert_dex_description_with_slippage, + formatArgs = wrappedList(token, slippage), + ), + ) + } else { + add(resourceReference(R.string.swapping_alert_dex_description, wrappedList(token))) + } } } - provider.slippage?.let { slippage -> - add(stringReference("\n\n")) - add( - resourceReference( - R.string.swapping_alert_slippage_description, - wrappedList("${slippage.parseBigDecimal(1)}$PERCENT"), - ), - ) - } } return uiState.copy( event = triggeredEvent( From 404bcc011abfac7511e21d92e9e4736726b9c7e3 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 5 Nov 2024 14:27:45 +0200 Subject: [PATCH 089/106] Updated on 2026-08-14 --- .../main/java/com/tangem/utils/extensions/BigDecimalExt.kt | 5 ++++- domain/staking/build.gradle.kts | 1 + .../domain/staking/InvalidatePendingTransactionsUseCase.kt | 3 ++- .../presentation/state/helpers/StakingBalanceUpdater.kt | 6 +++--- 4 files changed, 10 insertions(+), 5 deletions(-) diff --git a/core/utils/src/main/java/com/tangem/utils/extensions/BigDecimalExt.kt b/core/utils/src/main/java/com/tangem/utils/extensions/BigDecimalExt.kt index 31a31d6704..45f2966872 100644 --- a/core/utils/src/main/java/com/tangem/utils/extensions/BigDecimalExt.kt +++ b/core/utils/src/main/java/com/tangem/utils/extensions/BigDecimalExt.kt @@ -12,4 +12,7 @@ fun BigDecimal.isZero(): Boolean = this.compareTo(BigDecimal.ZERO) == 0 fun BigDecimal.isPositive(): Boolean = this.signum() == 1 /** Removes trailing zeros and returns plain [String] */ -fun BigDecimal.stripZeroPlainString(): String = this.stripTrailingZeros().toPlainString() \ No newline at end of file +fun BigDecimal.stripZeroPlainString(): String = this.stripTrailingZeros().toPlainString() + +/** Compares two [BigDecimal] numbers */ +infix fun BigDecimal.isEqualTo(other: BigDecimal): Boolean = this.compareTo(other) == 0 \ No newline at end of file diff --git a/domain/staking/build.gradle.kts b/domain/staking/build.gradle.kts index 9832ec7010..a0caf393f8 100644 --- a/domain/staking/build.gradle.kts +++ b/domain/staking/build.gradle.kts @@ -13,6 +13,7 @@ dependencies { api(projects.domain.staking.models) api(projects.domain.core) api(projects.core.analytics) + api(projects.core.utils) implementation(deps.kotlin.serialization) implementation(deps.jodatime) diff --git a/domain/staking/src/main/java/com/tangem/domain/staking/InvalidatePendingTransactionsUseCase.kt b/domain/staking/src/main/java/com/tangem/domain/staking/InvalidatePendingTransactionsUseCase.kt index 1b0f6b9128..dbb7c578d4 100644 --- a/domain/staking/src/main/java/com/tangem/domain/staking/InvalidatePendingTransactionsUseCase.kt +++ b/domain/staking/src/main/java/com/tangem/domain/staking/InvalidatePendingTransactionsUseCase.kt @@ -7,6 +7,7 @@ import com.tangem.domain.staking.model.stakekit.StakingError import com.tangem.domain.staking.model.stakekit.action.StakingAction import com.tangem.domain.staking.model.stakekit.action.StakingActionType import com.tangem.domain.staking.repositories.StakingErrorResolver +import com.tangem.utils.extensions.isEqualTo import java.util.UUID class InvalidatePendingTransactionsUseCase( @@ -75,7 +76,7 @@ class InvalidatePendingTransactionsUseCase( private fun modifyBalancesByStatus(balances: MutableList, action: StakingAction, type: BalanceType) { val index = balances.indexOfFirst { - !it.isPending && it.amount == action.amount && it.type == type + !it.isPending && it.amount isEqualTo action.amount && it.type == type } if (index != -1) { diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/helpers/StakingBalanceUpdater.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/helpers/StakingBalanceUpdater.kt index e10d32b8a1..c84db7f3ea 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/helpers/StakingBalanceUpdater.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/helpers/StakingBalanceUpdater.kt @@ -59,9 +59,6 @@ internal class StakingBalanceUpdater @AssistedInject constructor( suspend fun partialUpdate() { coroutineScope { listOf( - async { - updateStakeBalance() - }, async { updateNetworkStatuses(delay = 0) }, @@ -78,6 +75,9 @@ internal class StakingBalanceUpdater @AssistedInject constructor( async { updateStakeBalance() }, + async { + updateProcessingActions() + }, ).awaitAll() } } From 7ed060a37dac4cca27a623fdebf1f19426eefd23 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 5 Nov 2024 00:41:17 +0500 Subject: [PATCH 090/106] Updated on 2026-08-14 --- .../tap/di/domain/TokensDomainModule.kt | 12 ++++++ .../converters/AmountReduceByTransformer.kt | 29 +++++++++++-- .../converters/AmountReduceToTransformer.kt | 31 ++++++++++++-- .../converters/AmountStateConverter.kt | 4 +- .../converters/MaxEnterAmountConverter.kt | 10 ++--- .../field/AmountFieldChangeTransformer.kt | 34 +++++++++++---- .../AmountFieldSetMaxAmountTransformer.kt | 42 ++++++++++++++----- .../models/EnterAmountBoundary.kt | 22 ++++++++++ .../ui/amountScreen/models/MaxEnterAmount.kt | 9 ---- .../ui/amountScreen/utils/AmountUtils.kt | 13 +++--- .../common/ui/notifications/NotificationUM.kt | 8 ++++ .../ui/notifications/NotificationsFactory.kt | 14 +++++++ .../DefaultCurrencyChecksRepository.kt | 10 +++++ .../model/warnings/CryptoCurrencyCheck.kt | 1 + .../domain/tokens/GetCurrencyCheckUseCase.kt | 2 + .../GetMinimumTransactionAmountSyncUseCase.kt | 26 ++++++++++++ .../repository/CurrencyChecksRepository.kt | 3 ++ .../state/amount/AmountStateFactory.kt | 6 +++ .../amount/SendAmountReduceByConverter.kt | 8 +++- .../amount/SendAmountReduceToConverter.kt | 8 +++- .../state/confirm/SendNotificationFactory.kt | 6 +++ .../fields/SendAmountFieldChangeConverter.kt | 13 +++++- .../SendAmountFieldMaxAmountConverter.kt | 9 +++- .../presentation/viewmodel/SendViewModel.kt | 25 ++++++++++- .../transformers/SetAmountDataTransformer.kt | 4 +- .../SetInitialDataStateTransformer.kt | 4 +- .../amount/AmountChangeStateTransformer.kt | 12 ++++-- .../amount/AmountMaxValueStateTransformer.kt | 12 ++++-- .../amount/AmountReduceByStateTransformer.kt | 8 +++- .../amount/AmountReduceToStateTransformer.kt | 8 +++- .../viewmodel/StakingViewModel.kt | 24 ++++++++++- 31 files changed, 351 insertions(+), 66 deletions(-) create mode 100644 common/ui/src/main/java/com/tangem/common/ui/amountScreen/models/EnterAmountBoundary.kt delete mode 100644 common/ui/src/main/java/com/tangem/common/ui/amountScreen/models/MaxEnterAmount.kt create mode 100644 domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetMinimumTransactionAmountSyncUseCase.kt diff --git a/app/src/main/java/com/tangem/tap/di/domain/TokensDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/TokensDomainModule.kt index 3b3adab9bf..0776b43400 100644 --- a/app/src/main/java/com/tangem/tap/di/domain/TokensDomainModule.kt +++ b/app/src/main/java/com/tangem/tap/di/domain/TokensDomainModule.kt @@ -273,6 +273,18 @@ internal object TokensDomainModule { ) } + @Provides + @Singleton + fun provideGetMinimumTransactionAmountSyncUseCase( + currencyChecksRepository: CurrencyChecksRepository, + dispatchers: CoroutineDispatcherProvider, + ): GetMinimumTransactionAmountSyncUseCase { + return GetMinimumTransactionAmountSyncUseCase( + currencyChecksRepository = currencyChecksRepository, + dispatchers = dispatchers, + ) + } + @Provides @Singleton fun provideIsCryptoCurrencyCoinCouldHideUseCase( diff --git a/common/ui/src/main/java/com/tangem/common/ui/amountScreen/converters/AmountReduceByTransformer.kt b/common/ui/src/main/java/com/tangem/common/ui/amountScreen/converters/AmountReduceByTransformer.kt index 0b9f4c1a5d..4fbf0d991a 100644 --- a/common/ui/src/main/java/com/tangem/common/ui/amountScreen/converters/AmountReduceByTransformer.kt +++ b/common/ui/src/main/java/com/tangem/common/ui/amountScreen/converters/AmountReduceByTransformer.kt @@ -4,10 +4,15 @@ import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.ui.text.input.KeyboardType import com.tangem.common.ui.R import com.tangem.common.ui.amountScreen.models.AmountState +import com.tangem.common.ui.amountScreen.models.EnterAmountBoundary import com.tangem.common.ui.amountScreen.utils.checkExceedBalance import com.tangem.common.ui.amountScreen.utils.getFiatValue import com.tangem.common.ui.amountScreen.utils.getKeyboardAction +import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.extensions.resourceReference +import com.tangem.core.ui.extensions.wrappedList +import com.tangem.core.ui.format.bigdecimal.crypto +import com.tangem.core.ui.format.bigdecimal.format import com.tangem.core.ui.utils.parseBigDecimal import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.utils.isNullOrZero @@ -22,6 +27,7 @@ import java.math.BigDecimal */ class AmountReduceByTransformer( private val cryptoCurrencyStatus: CryptoCurrencyStatus, + private val minimumTransactionAmount: EnterAmountBoundary?, private val value: ReduceByData, ) : Transformer { @@ -47,22 +53,37 @@ class AmountReduceByTransformer( val checkValue = if (amountTextField.isFiatValue) fiatValue else cryptoValue val isExceedBalance = checkValue.checkExceedBalance(maxEnterAmount, amountTextField) + val isLessThanMinimumIfProvided = minimumTransactionAmount?.amount?.let { decimalCryptoValue < it } ?: false val isZero = if (amountTextField.isFiatValue) { decimalFiatValue.isNullOrZero() } else { decimalCryptoValue.isNullOrZero() } + val isCheckFailed = isExceedBalance || isLessThanMinimumIfProvided return prevState.copy( - isPrimaryButtonEnabled = !isExceedBalance && !isZero, + isPrimaryButtonEnabled = !isZero && !isCheckFailed, amountTextField = amountTextField.copy( value = cryptoValue, fiatValue = fiatValue, - isError = isExceedBalance, - error = resourceReference(R.string.send_validation_amount_exceeds_balance), + isError = isCheckFailed, + error = when { + isExceedBalance -> resourceReference(R.string.send_validation_amount_exceeds_balance) + isLessThanMinimumIfProvided -> { + val minimumAmount = minimumTransactionAmount + ?.amount + ?.format { crypto(cryptoCurrencyStatus.currency) } + .orEmpty() + resourceReference( + R.string.transfer_notification_invalid_minimum_transaction_amount_text, + wrappedList(minimumAmount, minimumAmount), + ) + } + else -> TextReference.EMPTY + }, cryptoAmount = amountTextField.cryptoAmount.copy(value = decimalCryptoValue), fiatAmount = amountTextField.fiatAmount.copy(value = decimalFiatValue), keyboardOptions = KeyboardOptions( - imeAction = getKeyboardAction(isExceedBalance, decimalCryptoValue), + imeAction = getKeyboardAction(isCheckFailed, decimalCryptoValue), keyboardType = KeyboardType.Number, ), ), diff --git a/common/ui/src/main/java/com/tangem/common/ui/amountScreen/converters/AmountReduceToTransformer.kt b/common/ui/src/main/java/com/tangem/common/ui/amountScreen/converters/AmountReduceToTransformer.kt index 74f6e66852..7e5cf0ab8d 100644 --- a/common/ui/src/main/java/com/tangem/common/ui/amountScreen/converters/AmountReduceToTransformer.kt +++ b/common/ui/src/main/java/com/tangem/common/ui/amountScreen/converters/AmountReduceToTransformer.kt @@ -4,11 +4,17 @@ import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.ui.text.input.KeyboardType import com.tangem.common.ui.R import com.tangem.common.ui.amountScreen.models.AmountState +import com.tangem.common.ui.amountScreen.models.EnterAmountBoundary import com.tangem.common.ui.amountScreen.utils.checkExceedBalance import com.tangem.common.ui.amountScreen.utils.getFiatValue import com.tangem.common.ui.amountScreen.utils.getKeyboardAction +import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.extensions.resourceReference +import com.tangem.core.ui.extensions.wrappedList +import com.tangem.core.ui.format.bigdecimal.crypto +import com.tangem.core.ui.format.bigdecimal.format import com.tangem.core.ui.utils.parseBigDecimal +import com.tangem.core.ui.utils.parseToBigDecimal import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.utils.isNullOrZero import com.tangem.utils.transformer.Transformer @@ -22,6 +28,7 @@ import java.math.BigDecimal */ class AmountReduceToTransformer( private val cryptoCurrencyStatus: CryptoCurrencyStatus, + private val minimumTransactionAmount: EnterAmountBoundary?, private val value: BigDecimal, ) : Transformer { private val maxEnterAmountConverter = MaxEnterAmountConverter() @@ -34,6 +41,7 @@ class AmountReduceToTransformer( val fiatDecimals = amountTextField.fiatAmount.decimals val cryptoValue = value.parseBigDecimal(cryptoDecimals) + val decimalCryptoValue = cryptoValue.parseToBigDecimal(cryptoDecimals) val (fiatValue, decimalFiatValue) = cryptoValue.getFiatValue( fiatRate = cryptoCurrencyStatus.value.fiatRate, isFiatValue = false, @@ -44,18 +52,33 @@ class AmountReduceToTransformer( val checkValue = if (amountTextField.isFiatValue) fiatValue else cryptoValue val isExceedBalance = checkValue.checkExceedBalance(maxEnterAmount, amountTextField) + val isLessThanMinimumIfProvided = minimumTransactionAmount?.amount?.let { decimalCryptoValue < it } ?: false val isZero = if (amountTextField.isFiatValue) decimalFiatValue.isNullOrZero() else value.isNullOrZero() + val isCheckFailed = isExceedBalance || isLessThanMinimumIfProvided return prevState.copy( - isPrimaryButtonEnabled = !isExceedBalance && !isZero, + isPrimaryButtonEnabled = !isZero && !isCheckFailed, amountTextField = amountTextField.copy( value = cryptoValue, fiatValue = fiatValue, - isError = isExceedBalance, - error = resourceReference(R.string.send_validation_amount_exceeds_balance), + isError = isCheckFailed, + error = when { + isExceedBalance -> resourceReference(R.string.send_validation_amount_exceeds_balance) + isLessThanMinimumIfProvided -> { + val minimumAmount = minimumTransactionAmount + ?.amount + ?.format { crypto(cryptoCurrencyStatus.currency) } + .orEmpty() + resourceReference( + R.string.transfer_notification_invalid_minimum_transaction_amount_text, + wrappedList(minimumAmount, minimumAmount), + ) + } + else -> TextReference.EMPTY + }, cryptoAmount = amountTextField.cryptoAmount.copy(value = value), fiatAmount = amountTextField.fiatAmount.copy(value = decimalFiatValue), keyboardOptions = KeyboardOptions( - imeAction = getKeyboardAction(isExceedBalance, value), + imeAction = getKeyboardAction(isCheckFailed, decimalCryptoValue), keyboardType = KeyboardType.Number, ), ), diff --git a/common/ui/src/main/java/com/tangem/common/ui/amountScreen/converters/AmountStateConverter.kt b/common/ui/src/main/java/com/tangem/common/ui/amountScreen/converters/AmountStateConverter.kt index d463a635bc..f91822b7b7 100644 --- a/common/ui/src/main/java/com/tangem/common/ui/amountScreen/converters/AmountStateConverter.kt +++ b/common/ui/src/main/java/com/tangem/common/ui/amountScreen/converters/AmountStateConverter.kt @@ -6,7 +6,7 @@ import com.tangem.common.ui.amountScreen.converters.field.AmountFieldConverter import com.tangem.common.ui.amountScreen.models.AmountParameters import com.tangem.common.ui.amountScreen.models.AmountSegmentedButtonsConfig import com.tangem.common.ui.amountScreen.models.AmountState -import com.tangem.common.ui.amountScreen.models.MaxEnterAmount +import com.tangem.common.ui.amountScreen.models.EnterAmountBoundary import com.tangem.core.ui.components.currency.icon.converter.CryptoCurrencyToIconStateConverter import com.tangem.core.ui.extensions.resourceReference import com.tangem.core.ui.extensions.stringReference @@ -34,7 +34,7 @@ class AmountStateConverter( private val clickIntents: AmountScreenClickIntents, private val appCurrencyProvider: Provider, private val cryptoCurrencyStatusProvider: Provider, - private val maxEnterAmount: MaxEnterAmount, + private val maxEnterAmount: EnterAmountBoundary, private val iconStateConverter: CryptoCurrencyToIconStateConverter, ) : Converter { diff --git a/common/ui/src/main/java/com/tangem/common/ui/amountScreen/converters/MaxEnterAmountConverter.kt b/common/ui/src/main/java/com/tangem/common/ui/amountScreen/converters/MaxEnterAmountConverter.kt index 1b23b64181..dce1957361 100644 --- a/common/ui/src/main/java/com/tangem/common/ui/amountScreen/converters/MaxEnterAmountConverter.kt +++ b/common/ui/src/main/java/com/tangem/common/ui/amountScreen/converters/MaxEnterAmountConverter.kt @@ -1,16 +1,16 @@ package com.tangem.common.ui.amountScreen.converters -import com.tangem.common.ui.amountScreen.models.MaxEnterAmount +import com.tangem.common.ui.amountScreen.models.EnterAmountBoundary import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.utils.converter.Converter /** - * Converts [CryptoCurrencyStatus] to [MaxEnterAmount] + * Converts [CryptoCurrencyStatus] to [EnterAmountBoundary] */ -class MaxEnterAmountConverter : Converter { +class MaxEnterAmountConverter : Converter { - override fun convert(value: CryptoCurrencyStatus): MaxEnterAmount { - return MaxEnterAmount( + override fun convert(value: CryptoCurrencyStatus): EnterAmountBoundary { + return EnterAmountBoundary( amount = value.value.amount, fiatAmount = value.value.fiatAmount, fiatRate = value.value.fiatRate, diff --git a/common/ui/src/main/java/com/tangem/common/ui/amountScreen/converters/field/AmountFieldChangeTransformer.kt b/common/ui/src/main/java/com/tangem/common/ui/amountScreen/converters/field/AmountFieldChangeTransformer.kt index b51fa49ad2..392fa817ba 100644 --- a/common/ui/src/main/java/com/tangem/common/ui/amountScreen/converters/field/AmountFieldChangeTransformer.kt +++ b/common/ui/src/main/java/com/tangem/common/ui/amountScreen/converters/field/AmountFieldChangeTransformer.kt @@ -5,14 +5,18 @@ import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.input.KeyboardType import com.tangem.common.ui.R import com.tangem.common.ui.amountScreen.models.AmountState -import com.tangem.common.ui.amountScreen.models.MaxEnterAmount +import com.tangem.common.ui.amountScreen.models.EnterAmountBoundary import com.tangem.common.ui.amountScreen.utils.checkExceedBalance import com.tangem.common.ui.amountScreen.utils.getCryptoValue import com.tangem.common.ui.amountScreen.utils.getFiatValue import com.tangem.common.ui.amountScreen.utils.getKeyboardAction import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.extensions.resourceReference +import com.tangem.core.ui.extensions.wrappedList +import com.tangem.core.ui.format.bigdecimal.crypto +import com.tangem.core.ui.format.bigdecimal.format import com.tangem.core.ui.utils.parseToBigDecimal +import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.utils.isNullOrZero import com.tangem.utils.transformer.Transformer import java.math.BigDecimal @@ -24,7 +28,9 @@ import java.math.BigDecimal * @property value amount value */ class AmountFieldChangeTransformer( - private val maxEnterAmount: MaxEnterAmount, + private val cryptoCurrencyStatus: CryptoCurrencyStatus, + private val maxEnterAmount: EnterAmountBoundary, + private val minimumTransactionAmount: EnterAmountBoundary?, private val value: String, ) : Transformer { @@ -52,23 +58,37 @@ class AmountFieldChangeTransformer( val checkValue = if (amountTextField.isFiatValue) fiatValue else cryptoValue val isExceedBalance = checkValue.checkExceedBalance(maxEnterAmount, amountTextField) + val isLessThanMinimumIfProvided = minimumTransactionAmount?.amount?.let { decimalCryptoValue < it } ?: false val isZero = if (amountTextField.isFiatValue) { decimalFiatValue.isNullOrZero() } else { decimalCryptoValue.isNullOrZero() } + val isCheckFailed = isExceedBalance || isLessThanMinimumIfProvided return prevState.copy( - isPrimaryButtonEnabled = !isExceedBalance && !isZero, + isPrimaryButtonEnabled = !isZero && !isCheckFailed, amountTextField = amountTextField.copy( value = cryptoValue, fiatValue = fiatValue, - isError = isExceedBalance, - error = resourceReference(R.string.send_validation_amount_exceeds_balance).takeIf { isExceedBalance } - ?: TextReference.EMPTY, + isError = isCheckFailed, + error = when { + isExceedBalance -> resourceReference(R.string.send_validation_amount_exceeds_balance) + isLessThanMinimumIfProvided -> { + val minimumAmount = minimumTransactionAmount + ?.amount + ?.format { crypto(cryptoCurrencyStatus.currency) } + .orEmpty() + resourceReference( + R.string.transfer_notification_invalid_minimum_transaction_amount_text, + wrappedList(minimumAmount, minimumAmount), + ) + } + else -> TextReference.EMPTY + }, cryptoAmount = amountTextField.cryptoAmount.copy(value = decimalCryptoValue), fiatAmount = amountTextField.fiatAmount.copy(value = decimalFiatValue), keyboardOptions = KeyboardOptions( - imeAction = getKeyboardAction(isExceedBalance, decimalCryptoValue), + imeAction = getKeyboardAction(isCheckFailed, decimalCryptoValue), keyboardType = KeyboardType.Number, ), ), diff --git a/common/ui/src/main/java/com/tangem/common/ui/amountScreen/converters/field/AmountFieldSetMaxAmountTransformer.kt b/common/ui/src/main/java/com/tangem/common/ui/amountScreen/converters/field/AmountFieldSetMaxAmountTransformer.kt index 16c67d20f8..990e027cdf 100644 --- a/common/ui/src/main/java/com/tangem/common/ui/amountScreen/converters/field/AmountFieldSetMaxAmountTransformer.kt +++ b/common/ui/src/main/java/com/tangem/common/ui/amountScreen/converters/field/AmountFieldSetMaxAmountTransformer.kt @@ -1,12 +1,19 @@ package com.tangem.common.ui.amountScreen.converters.field import androidx.compose.foundation.text.KeyboardOptions -import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.input.KeyboardType +import com.tangem.common.ui.R import com.tangem.common.ui.amountScreen.models.AmountState -import com.tangem.common.ui.amountScreen.models.MaxEnterAmount +import com.tangem.common.ui.amountScreen.models.EnterAmountBoundary +import com.tangem.common.ui.amountScreen.utils.getKeyboardAction +import com.tangem.core.ui.extensions.TextReference +import com.tangem.core.ui.extensions.resourceReference +import com.tangem.core.ui.extensions.wrappedList +import com.tangem.core.ui.format.bigdecimal.crypto +import com.tangem.core.ui.format.bigdecimal.format import com.tangem.core.ui.utils.parseBigDecimal -import com.tangem.utils.isNullOrZero +import com.tangem.domain.tokens.model.CryptoCurrencyStatus +import com.tangem.utils.extensions.isZero import com.tangem.utils.transformer.Transformer import java.math.RoundingMode @@ -16,7 +23,9 @@ import java.math.RoundingMode * @property maxAmount maximum enter amount */ class AmountFieldSetMaxAmountTransformer( - private val maxAmount: MaxEnterAmount, + private val cryptoCurrencyStatus: CryptoCurrencyStatus, + private val maxAmount: EnterAmountBoundary, + private val minAmount: EnterAmountBoundary?, ) : Transformer { override fun transform(prevState: AmountState): AmountState { @@ -29,22 +38,35 @@ class AmountFieldSetMaxAmountTransformer( val decimalCryptoValue = maxAmount.amount val decimalFiatValue = maxAmount.fiatAmount - if (decimalCryptoValue.isNullOrZero()) return prevState + if (decimalCryptoValue == null || decimalCryptoValue.isZero()) return prevState - val isDoneActionEnabled = !decimalCryptoValue.isNullOrZero() - val cryptoValue = decimalCryptoValue?.parseBigDecimal(cryptoDecimals).orEmpty() + val cryptoValue = decimalCryptoValue.parseBigDecimal(cryptoDecimals) val fiatValue = decimalFiatValue?.parseBigDecimal(fiatDecimals, roundingMode = RoundingMode.HALF_UP).orEmpty() + val isLessThanMinimumIfProvided = minAmount?.amount?.let { decimalCryptoValue < it } ?: false return prevState.copy( - isPrimaryButtonEnabled = true, + isPrimaryButtonEnabled = !isLessThanMinimumIfProvided, amountTextField = amountTextField.copy( isValuePasted = true, value = cryptoValue, fiatValue = fiatValue, - isError = false, + isError = isLessThanMinimumIfProvided, + error = when { + isLessThanMinimumIfProvided -> { + val minimumAmount = minAmount + ?.amount + ?.format { crypto(cryptoCurrencyStatus.currency) } + .orEmpty() + resourceReference( + R.string.transfer_notification_invalid_minimum_transaction_amount_text, + wrappedList(minimumAmount, minimumAmount), + ) + } + else -> TextReference.EMPTY + }, cryptoAmount = amountTextField.cryptoAmount.copy(value = decimalCryptoValue), fiatAmount = amountTextField.fiatAmount.copy(value = decimalFiatValue), keyboardOptions = KeyboardOptions( - imeAction = if (isDoneActionEnabled) ImeAction.Done else ImeAction.None, + imeAction = getKeyboardAction(isLessThanMinimumIfProvided, decimalCryptoValue), keyboardType = KeyboardType.Number, ), ), diff --git a/common/ui/src/main/java/com/tangem/common/ui/amountScreen/models/EnterAmountBoundary.kt b/common/ui/src/main/java/com/tangem/common/ui/amountScreen/models/EnterAmountBoundary.kt new file mode 100644 index 0000000000..bc21ab5104 --- /dev/null +++ b/common/ui/src/main/java/com/tangem/common/ui/amountScreen/models/EnterAmountBoundary.kt @@ -0,0 +1,22 @@ +package com.tangem.common.ui.amountScreen.models + +import java.math.BigDecimal + +data class EnterAmountBoundary( + val amount: BigDecimal? = null, + val fiatAmount: BigDecimal? = null, + val fiatRate: BigDecimal? = null, +) { + constructor( + amount: BigDecimal? = null, + fiatRate: BigDecimal? = null, + ) : this( + amount = amount, + fiatAmount = if (amount != null && fiatRate != null) { + amount * fiatRate + } else { + null + }, + fiatRate = fiatRate, + ) +} \ No newline at end of file diff --git a/common/ui/src/main/java/com/tangem/common/ui/amountScreen/models/MaxEnterAmount.kt b/common/ui/src/main/java/com/tangem/common/ui/amountScreen/models/MaxEnterAmount.kt deleted file mode 100644 index e9a5f88238..0000000000 --- a/common/ui/src/main/java/com/tangem/common/ui/amountScreen/models/MaxEnterAmount.kt +++ /dev/null @@ -1,9 +0,0 @@ -package com.tangem.common.ui.amountScreen.models - -import java.math.BigDecimal - -data class MaxEnterAmount( - val amount: BigDecimal? = null, - val fiatAmount: BigDecimal? = null, - val fiatRate: BigDecimal? = null, -) \ No newline at end of file diff --git a/common/ui/src/main/java/com/tangem/common/ui/amountScreen/utils/AmountUtils.kt b/common/ui/src/main/java/com/tangem/common/ui/amountScreen/utils/AmountUtils.kt index 3f20ba474c..fe59dbf317 100644 --- a/common/ui/src/main/java/com/tangem/common/ui/amountScreen/utils/AmountUtils.kt +++ b/common/ui/src/main/java/com/tangem/common/ui/amountScreen/utils/AmountUtils.kt @@ -2,10 +2,10 @@ package com.tangem.common.ui.amountScreen.utils import androidx.compose.ui.text.input.ImeAction import com.tangem.common.ui.amountScreen.models.AmountFieldModel -import com.tangem.common.ui.amountScreen.models.MaxEnterAmount +import com.tangem.common.ui.amountScreen.models.EnterAmountBoundary import com.tangem.core.ui.utils.parseBigDecimal import com.tangem.core.ui.utils.parseToBigDecimal -import com.tangem.utils.isNullOrZero +import com.tangem.utils.extensions.isZero import java.math.BigDecimal import java.math.RoundingMode @@ -36,7 +36,10 @@ internal fun String.getFiatValue( } } -internal fun String.checkExceedBalance(maxEnterAmount: MaxEnterAmount, amountTextField: AmountFieldModel): Boolean { +internal fun String.checkExceedBalance( + maxEnterAmount: EnterAmountBoundary, + amountTextField: AmountFieldModel, +): Boolean { val currencyCryptoAmount = maxEnterAmount.amount ?: BigDecimal.ZERO val currencyFiatAmount = maxEnterAmount.fiatAmount ?: BigDecimal.ZERO val fiatDecimal = parseToBigDecimal(amountTextField.fiatAmount.decimals) @@ -48,8 +51,8 @@ internal fun String.checkExceedBalance(maxEnterAmount: MaxEnterAmount, amountTex } } -internal fun getKeyboardAction(isExceedBalance: Boolean, decimalCryptoValue: BigDecimal) = - if (!isExceedBalance && !decimalCryptoValue.isNullOrZero()) { +internal fun getKeyboardAction(isCheckFailed: Boolean, decimalCryptoValue: BigDecimal) = + if (!isCheckFailed && !decimalCryptoValue.isZero()) { ImeAction.Done } else { ImeAction.None diff --git a/common/ui/src/main/java/com/tangem/common/ui/notifications/NotificationUM.kt b/common/ui/src/main/java/com/tangem/common/ui/notifications/NotificationUM.kt index 8ca24539b5..858fdccb46 100644 --- a/common/ui/src/main/java/com/tangem/common/ui/notifications/NotificationUM.kt +++ b/common/ui/src/main/java/com/tangem/common/ui/notifications/NotificationUM.kt @@ -47,6 +47,14 @@ sealed class NotificationUM(val config: NotificationConfig) { ), ) + data class MinimumSendAmountError(val amount: String) : Error( + title = resourceReference(R.string.send_notification_invalid_amount_title), + subtitle = resourceReference( + R.string.transfer_notification_invalid_minimum_transaction_amount_text, + wrappedList(amount, amount), + ), + ) + data class TransactionLimitError( val cryptoCurrency: String, val utxoLimit: String, diff --git a/common/ui/src/main/java/com/tangem/common/ui/notifications/NotificationsFactory.kt b/common/ui/src/main/java/com/tangem/common/ui/notifications/NotificationsFactory.kt index 5ba4b5f528..359d6dd9c0 100644 --- a/common/ui/src/main/java/com/tangem/common/ui/notifications/NotificationsFactory.kt +++ b/common/ui/src/main/java/com/tangem/common/ui/notifications/NotificationsFactory.kt @@ -75,6 +75,20 @@ object NotificationsFactory { } } + fun MutableList.addMinimumAmountErrorNotification( + minimumSendAmount: BigDecimal?, + sendingAmount: BigDecimal, + cryptoCurrency: CryptoCurrency, + ) { + if (minimumSendAmount != null && minimumSendAmount > sendingAmount) { + add( + NotificationUM.Error.MinimumSendAmountError( + amount = minimumSendAmount.format { crypto(cryptoCurrency) }, + ), + ) + } + } + fun MutableList.addTransactionLimitErrorNotification( utxoLimit: UtxoAmountLimit?, cryptoCurrency: CryptoCurrency, diff --git a/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultCurrencyChecksRepository.kt b/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultCurrencyChecksRepository.kt index ea56a8adbf..b156d85caf 100644 --- a/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultCurrencyChecksRepository.kt +++ b/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultCurrencyChecksRepository.kt @@ -2,6 +2,7 @@ package com.tangem.data.tokens.repository import com.tangem.blockchain.blockchains.polkadot.ExistentialDepositProvider import com.tangem.blockchain.common.FeeResourceAmountProvider +import com.tangem.blockchain.common.MinimumSendAmountProvider import com.tangem.blockchain.common.ReserveAmountProvider import com.tangem.blockchain.common.UtxoAmountLimitProvider import com.tangem.data.tokens.converters.UtxoConverter @@ -43,6 +44,15 @@ internal class DefaultCurrencyChecksRepository( return if (manager is ReserveAmountProvider) manager.getReserveAmount() else null } + override suspend fun getMinimumSendAmount(userWalletId: UserWalletId, network: Network): BigDecimal? { + val manager = walletManagersFacade.getOrCreateWalletManager( + userWalletId = userWalletId, + network = network, + ) + + return if (manager is MinimumSendAmountProvider) manager.getMinimumSendAmount() else null + } + override suspend fun getFeeResourceAmount(userWalletId: UserWalletId, network: Network): CurrencyAmount? { val manager = walletManagersFacade.getOrCreateWalletManager( userWalletId = userWalletId, diff --git a/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/warnings/CryptoCurrencyCheck.kt b/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/warnings/CryptoCurrencyCheck.kt index 780c6c48fe..dae8238b02 100644 --- a/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/warnings/CryptoCurrencyCheck.kt +++ b/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/warnings/CryptoCurrencyCheck.kt @@ -6,6 +6,7 @@ import java.math.BigDecimal data class CryptoCurrencyCheck( val dustValue: BigDecimal?, val reserveAmount: BigDecimal?, + val minimumSendAmount: BigDecimal?, val existentialDeposit: BigDecimal?, val utxoAmountLimit: UtxoAmountLimit?, val isAccountFunded: Boolean, diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCurrencyCheckUseCase.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCurrencyCheckUseCase.kt index aa488b2671..ba9e837248 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCurrencyCheckUseCase.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCurrencyCheckUseCase.kt @@ -24,6 +24,7 @@ class GetCurrencyCheckUseCase( val network = currencyStatus.currency.network val dustValue = currencyChecksRepository.getDustValue(userWalletId, network) val reserveAmount = currencyChecksRepository.getReserveAmount(userWalletId, network) + val minimumSendAmount = currencyChecksRepository.getMinimumSendAmount(userWalletId, network) val existentialDeposit = currencyChecksRepository.getExistentialDeposit(userWalletId, network) val isAccountFunded = recipientAddress?.let { currencyChecksRepository.checkIfAccountFunded( @@ -46,6 +47,7 @@ class GetCurrencyCheckUseCase( CryptoCurrencyCheck( dustValue = dustValue, reserveAmount = reserveAmount, + minimumSendAmount = minimumSendAmount, existentialDeposit = existentialDeposit, utxoAmountLimit = utxoAmountLimit, isAccountFunded = isAccountFunded, diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetMinimumTransactionAmountSyncUseCase.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetMinimumTransactionAmountSyncUseCase.kt new file mode 100644 index 0000000000..73aaeb8c0d --- /dev/null +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetMinimumTransactionAmountSyncUseCase.kt @@ -0,0 +1,26 @@ +package com.tangem.domain.tokens + +import arrow.core.Either +import arrow.core.raise.either +import com.tangem.domain.tokens.model.CryptoCurrencyStatus +import com.tangem.domain.tokens.repository.CurrencyChecksRepository +import com.tangem.domain.wallets.models.UserWalletId +import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import kotlinx.coroutines.withContext +import java.math.BigDecimal + +class GetMinimumTransactionAmountSyncUseCase( + private val currencyChecksRepository: CurrencyChecksRepository, + private val dispatchers: CoroutineDispatcherProvider, +) { + + suspend operator fun invoke( + userWalletId: UserWalletId, + cryptoCurrencyStatus: CryptoCurrencyStatus, + ): Either = withContext(dispatchers.io) { + either { + val cryptoCurrency = cryptoCurrencyStatus.currency + currencyChecksRepository.getMinimumSendAmount(userWalletId, cryptoCurrency.network) + } + } +} \ No newline at end of file diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/CurrencyChecksRepository.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/CurrencyChecksRepository.kt index 0b5745a983..4ff37c6420 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/CurrencyChecksRepository.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/CurrencyChecksRepository.kt @@ -20,6 +20,9 @@ interface CurrencyChecksRepository { /** Returns reserve amount which is required to create an account */ suspend fun getReserveAmount(userWalletId: UserWalletId, network: Network): BigDecimal? + /** Returns minimum send transaction amount */ + suspend fun getMinimumSendAmount(userWalletId: UserWalletId, network: Network): BigDecimal? + /** Returns a fee resource amount available and max for paying fees in several blockchains */ suspend fun getFeeResourceAmount(userWalletId: UserWalletId, network: Network): CurrencyAmount? diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/AmountStateFactory.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/AmountStateFactory.kt index 204918b99b..9b8d11fb41 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/AmountStateFactory.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/AmountStateFactory.kt @@ -1,6 +1,7 @@ package com.tangem.features.send.impl.presentation.state.amount import com.tangem.common.ui.amountScreen.converters.AmountReduceByTransformer +import com.tangem.common.ui.amountScreen.models.EnterAmountBoundary import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.features.send.impl.presentation.state.SendUiState import com.tangem.features.send.impl.presentation.state.StateRouter @@ -16,6 +17,7 @@ internal class AmountStateFactory( private val stateRouterProvider: Provider, private val currentStateProvider: Provider, private val cryptoCurrencyStatusProvider: Provider, + private val minimumTransactionAmountProvider: Provider, ) { private val amountFieldChangeConverter by lazy(LazyThreadSafetyMode.NONE) { @@ -23,6 +25,7 @@ internal class AmountStateFactory( stateRouterProvider = stateRouterProvider, currentStateProvider = currentStateProvider, cryptoCurrencyStatusProvider = cryptoCurrencyStatusProvider, + minimumTransactionAmountProvider = minimumTransactionAmountProvider, ) } private val amountFieldMaxAmountConverter by lazy(LazyThreadSafetyMode.NONE) { @@ -30,6 +33,7 @@ internal class AmountStateFactory( stateRouterProvider = stateRouterProvider, currentStateProvider = currentStateProvider, cryptoCurrencyStatusProvider = cryptoCurrencyStatusProvider, + minimumTransactionAmountProvider = minimumTransactionAmountProvider, ) } @@ -51,6 +55,7 @@ internal class AmountStateFactory( stateRouterProvider = stateRouterProvider, currentStateProvider = currentStateProvider, cryptoCurrencyStatusProvider = cryptoCurrencyStatusProvider, + minimumTransactionAmountProvider = minimumTransactionAmountProvider, ) } private val amountReduceToConverter by lazy { @@ -58,6 +63,7 @@ internal class AmountStateFactory( stateRouterProvider = stateRouterProvider, currentStateProvider = currentStateProvider, cryptoCurrencyStatusProvider = cryptoCurrencyStatusProvider, + minimumTransactionAmountProvider = minimumTransactionAmountProvider, ) } diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/SendAmountReduceByConverter.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/SendAmountReduceByConverter.kt index 0468cd8c82..c0e28102e7 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/SendAmountReduceByConverter.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/SendAmountReduceByConverter.kt @@ -1,6 +1,7 @@ package com.tangem.features.send.impl.presentation.state.amount import com.tangem.common.ui.amountScreen.converters.AmountReduceByTransformer +import com.tangem.common.ui.amountScreen.models.EnterAmountBoundary import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.features.send.impl.presentation.state.SendUiState import com.tangem.features.send.impl.presentation.state.StateRouter @@ -11,6 +12,7 @@ internal class SendAmountReduceByConverter( private val stateRouterProvider: Provider, private val currentStateProvider: Provider, private val cryptoCurrencyStatusProvider: Provider, + private val minimumTransactionAmountProvider: Provider, ) : Converter { override fun convert(value: AmountReduceByTransformer.ReduceByData): SendUiState { @@ -23,7 +25,11 @@ internal class SendAmountReduceByConverter( sendState = state.sendState?.copy( reduceAmountBy = value.reduceAmountBy, ), - amountState = AmountReduceByTransformer(cryptoCurrencyStatusProvider(), value).transform(amountState), + amountState = AmountReduceByTransformer( + cryptoCurrencyStatus = cryptoCurrencyStatusProvider(), + minimumTransactionAmount = minimumTransactionAmountProvider(), + value = value, + ).transform(amountState), ) } } \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/SendAmountReduceToConverter.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/SendAmountReduceToConverter.kt index 1bb5518063..ea1caaf180 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/SendAmountReduceToConverter.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/SendAmountReduceToConverter.kt @@ -1,6 +1,7 @@ package com.tangem.features.send.impl.presentation.state.amount import com.tangem.common.ui.amountScreen.converters.AmountReduceToTransformer +import com.tangem.common.ui.amountScreen.models.EnterAmountBoundary import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.features.send.impl.presentation.state.SendUiState import com.tangem.features.send.impl.presentation.state.StateRouter @@ -12,6 +13,7 @@ internal class SendAmountReduceToConverter( private val stateRouterProvider: Provider, private val currentStateProvider: Provider, private val cryptoCurrencyStatusProvider: Provider, + private val minimumTransactionAmountProvider: Provider, ) : Converter { override fun convert(value: BigDecimal): SendUiState { @@ -21,7 +23,11 @@ internal class SendAmountReduceToConverter( return state.copyWrapped( isEditState = isEditState, - amountState = AmountReduceToTransformer(cryptoCurrencyStatusProvider(), value).transform(amountState), + amountState = AmountReduceToTransformer( + cryptoCurrencyStatus = cryptoCurrencyStatusProvider(), + minimumTransactionAmount = minimumTransactionAmountProvider(), + value = value, + ).transform(amountState), ) } } \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/confirm/SendNotificationFactory.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/confirm/SendNotificationFactory.kt index bfe558339a..9975712433 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/confirm/SendNotificationFactory.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/confirm/SendNotificationFactory.kt @@ -11,6 +11,7 @@ import com.tangem.common.ui.notifications.NotificationsFactory.addExceedsBalance import com.tangem.common.ui.notifications.NotificationsFactory.addExistentialWarningNotification import com.tangem.common.ui.notifications.NotificationsFactory.addFeeCoverageNotification import com.tangem.common.ui.notifications.NotificationsFactory.addFeeUnreachableNotification +import com.tangem.common.ui.notifications.NotificationsFactory.addMinimumAmountErrorNotification import com.tangem.common.ui.notifications.NotificationsFactory.addReserveAmountErrorNotification import com.tangem.common.ui.notifications.NotificationsFactory.addTransactionLimitErrorNotification import com.tangem.common.ui.notifications.NotificationsFactory.addValidateTransactionNotifications @@ -192,6 +193,11 @@ internal class SendNotificationFactory( cryptoCurrency = currency, isAccountFunded = currencyCheck.isAccountFunded, ) + addMinimumAmountErrorNotification( + minimumSendAmount = currencyCheck.minimumSendAmount, + sendingAmount = sendingAmount, + cryptoCurrency = currency, + ) } private suspend fun MutableList.addWarningNotifications( diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fields/SendAmountFieldChangeConverter.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fields/SendAmountFieldChangeConverter.kt index c4f28c469e..ea7e502aec 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fields/SendAmountFieldChangeConverter.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fields/SendAmountFieldChangeConverter.kt @@ -2,6 +2,7 @@ package com.tangem.features.send.impl.presentation.state.fields import com.tangem.common.ui.amountScreen.converters.MaxEnterAmountConverter import com.tangem.common.ui.amountScreen.converters.field.AmountFieldChangeTransformer +import com.tangem.common.ui.amountScreen.models.EnterAmountBoundary import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.features.send.impl.presentation.state.SendUiState import com.tangem.features.send.impl.presentation.state.StateRouter @@ -12,21 +13,29 @@ internal class SendAmountFieldChangeConverter( private val stateRouterProvider: Provider, private val currentStateProvider: Provider, private val cryptoCurrencyStatusProvider: Provider, + private val minimumTransactionAmountProvider: Provider, ) : Converter { private val maxEnterAmountConverter = MaxEnterAmountConverter() override fun convert(value: String): SendUiState { val state = currentStateProvider() + val cryptoCurrencyStatus = cryptoCurrencyStatusProvider() val isEditState = stateRouterProvider().isEditState val amountState = state.getAmountState(isEditState) - val maxEnterAmount = maxEnterAmountConverter.convert(cryptoCurrencyStatusProvider()) + val maxEnterAmount = maxEnterAmountConverter.convert(cryptoCurrencyStatus) + val minimumTransactionAmount = minimumTransactionAmountProvider() return state.copyWrapped( isEditState = isEditState, sendState = state.sendState?.copy(reduceAmountBy = null), - amountState = AmountFieldChangeTransformer(maxEnterAmount, value).transform(amountState), + amountState = AmountFieldChangeTransformer( + cryptoCurrencyStatus = cryptoCurrencyStatus, + maxEnterAmount = maxEnterAmount, + minimumTransactionAmount = minimumTransactionAmount, + value = value, + ).transform(amountState), ) } } \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fields/SendAmountFieldMaxAmountConverter.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fields/SendAmountFieldMaxAmountConverter.kt index 6e071d34d8..882365d726 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fields/SendAmountFieldMaxAmountConverter.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fields/SendAmountFieldMaxAmountConverter.kt @@ -2,6 +2,7 @@ package com.tangem.features.send.impl.presentation.state.fields import com.tangem.common.ui.amountScreen.converters.MaxEnterAmountConverter import com.tangem.common.ui.amountScreen.converters.field.AmountFieldSetMaxAmountTransformer +import com.tangem.common.ui.amountScreen.models.EnterAmountBoundary import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.features.send.impl.presentation.state.SendUiState import com.tangem.features.send.impl.presentation.state.StateRouter @@ -13,6 +14,7 @@ internal class SendAmountFieldMaxAmountConverter( private val stateRouterProvider: Provider, private val currentStateProvider: Provider, private val cryptoCurrencyStatusProvider: Provider, + private val minimumTransactionAmountProvider: Provider, ) : Converter { private val maxEnterAmountConverter = MaxEnterAmountConverter() @@ -27,11 +29,16 @@ internal class SendAmountFieldMaxAmountConverter( if (decimalCryptoValue.isNullOrZero()) return state val maxEnterAmount = maxEnterAmountConverter.convert(cryptoCurrencyStatus) + val minimumTransactionAmount = minimumTransactionAmountProvider() return state.copyWrapped( isEditState = isEditState, sendState = state.sendState?.copy(reduceAmountBy = null), - amountState = AmountFieldSetMaxAmountTransformer(maxEnterAmount).transform(amountState), + amountState = AmountFieldSetMaxAmountTransformer( + cryptoCurrencyStatus = cryptoCurrencyStatus, + maxAmount = maxEnterAmount, + minAmount = minimumTransactionAmount, + ).transform(amountState), ) } } \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendViewModel.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendViewModel.kt index 468482bb95..14b94f1d08 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendViewModel.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendViewModel.kt @@ -12,6 +12,7 @@ import com.tangem.blockchain.common.transaction.TransactionFee import com.tangem.common.routing.AppRoute import com.tangem.common.routing.bundle.unbundle import com.tangem.common.ui.amountScreen.models.AmountState +import com.tangem.common.ui.amountScreen.models.EnterAmountBoundary import com.tangem.common.ui.notifications.NotificationUM import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.core.ui.utils.parseBigDecimal @@ -59,6 +60,7 @@ import com.tangem.features.send.impl.presentation.state.recipient.RecipientSendF import com.tangem.lib.crypto.BlockchainUtils import com.tangem.utils.Provider import com.tangem.utils.coroutines.* +import com.tangem.utils.extensions.orZero import com.tangem.utils.extensions.stripZeroPlainString import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.* @@ -78,6 +80,7 @@ internal class SendViewModel @Inject constructor( private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase, private val getWalletsUseCase: GetWalletsUseCase, private val getFeePaidCryptoCurrencyStatusSyncUseCase: GetFeePaidCryptoCurrencyStatusSyncUseCase, + private val getMinimumTransactionAmountSyncUseCase: GetMinimumTransactionAmountSyncUseCase, private val getCryptoCurrencyUseCase: GetCryptoCurrencyUseCase, private val getNetworkAddressesUseCase: GetNetworkAddressesUseCase, private val getTxHistoryItemsCountUseCase: GetTxHistoryItemsCountUseCase, @@ -153,6 +156,7 @@ internal class SendViewModel @Inject constructor( stateRouterProvider = Provider { stateRouter }, currentStateProvider = Provider { uiState.value }, cryptoCurrencyStatusProvider = Provider { cryptoCurrencyStatus }, + minimumTransactionAmountProvider = Provider { minimumTransactionAmount }, ) private val feeStateFactory = FeeStateFactory( @@ -212,6 +216,7 @@ internal class SendViewModel @Inject constructor( private var isTapHelpPreviewEnabled: Boolean = false private var cryptoCurrencyStatus: CryptoCurrencyStatus by Delegates.notNull() private var feeCryptoCurrencyStatus: CryptoCurrencyStatus? = null + private var minimumTransactionAmount: EnterAmountBoundary? = null private var balanceJobHolder = JobHolder() private var balanceHidingJobHolder = JobHolder() @@ -295,6 +300,7 @@ internal class SendViewModel @Inject constructor( onDataLoaded( currencyStatus = cryptoCurrencyStatus, feeCurrencyStatus = getFeeCurrencyStatusSync(cryptoCurrencyStatus, isMultiCurrency), + minTransactionAmount = getMinimumTransactionAmount(cryptoCurrencyStatus), ) }, ifLeft = { showErrorAlert() }, @@ -336,6 +342,18 @@ internal class SendViewModel @Inject constructor( } } + private suspend fun getMinimumTransactionAmount(cryptoCurrencyStatus: CryptoCurrencyStatus): EnterAmountBoundary? { + return getMinimumTransactionAmountSyncUseCase( + userWalletId = userWalletId, + cryptoCurrencyStatus = cryptoCurrencyStatus, + ).getOrNull()?.let { + EnterAmountBoundary( + amount = it, + fiatRate = cryptoCurrencyStatus.value.fiatRate.orZero(), + ) + } + } + private fun createSelectedAppCurrencyFlow(): StateFlow { return getSelectedAppCurrencyUseCase() .map { maybeAppCurrency -> @@ -348,9 +366,14 @@ internal class SendViewModel @Inject constructor( ) } - private fun onDataLoaded(currencyStatus: CryptoCurrencyStatus, feeCurrencyStatus: CryptoCurrencyStatus?) { + private fun onDataLoaded( + currencyStatus: CryptoCurrencyStatus, + feeCurrencyStatus: CryptoCurrencyStatus?, + minTransactionAmount: EnterAmountBoundary?, + ) { cryptoCurrencyStatus = currencyStatus feeCryptoCurrencyStatus = feeCurrencyStatus + minimumTransactionAmount = minTransactionAmount subscribeOnQRScannerResult() when { uiState.value.sendState?.isSuccess == true -> return diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetAmountDataTransformer.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetAmountDataTransformer.kt index b9226a4214..532054c768 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetAmountDataTransformer.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetAmountDataTransformer.kt @@ -2,7 +2,7 @@ package com.tangem.features.staking.impl.presentation.state.transformers import com.tangem.common.ui.amountScreen.converters.AmountStateConverter import com.tangem.common.ui.amountScreen.models.AmountParameters -import com.tangem.common.ui.amountScreen.models.MaxEnterAmount +import com.tangem.common.ui.amountScreen.models.EnterAmountBoundary import com.tangem.core.ui.components.currency.icon.converter.CryptoCurrencyToIconStateConverter import com.tangem.core.ui.extensions.resourceReference import com.tangem.core.ui.extensions.stringReference @@ -37,7 +37,7 @@ internal class SetAmountDataTransformer( } else { cryptoBalanceValue.amount to cryptoBalanceValue.fiatAmount } - val maxEnterAmount = MaxEnterAmount( + val maxEnterAmount = EnterAmountBoundary( amount = amount, fiatAmount = fiatAmount, fiatRate = cryptoBalanceValue.fiatRate, diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetInitialDataStateTransformer.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetInitialDataStateTransformer.kt index ef2d4058db..6825aeb4f6 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetInitialDataStateTransformer.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetInitialDataStateTransformer.kt @@ -4,7 +4,7 @@ import com.tangem.common.extensions.remove import com.tangem.common.ui.amountScreen.converters.AmountStateConverter import com.tangem.common.ui.amountScreen.models.AmountParameters import com.tangem.common.ui.amountScreen.models.AmountState -import com.tangem.common.ui.amountScreen.models.MaxEnterAmount +import com.tangem.common.ui.amountScreen.models.EnterAmountBoundary import com.tangem.core.ui.components.currency.icon.converter.CryptoCurrencyToIconStateConverter import com.tangem.core.ui.components.list.RoundedListWithDividersItemData import com.tangem.core.ui.extensions.* @@ -201,7 +201,7 @@ internal class SetInitialDataStateTransformer( private fun createInitialAmountState(): AmountState { val cryptoBalanceValue = cryptoCurrencyStatusProvider().value - val maxEnterAmount = MaxEnterAmount( + val maxEnterAmount = EnterAmountBoundary( amount = cryptoBalanceValue.amount, fiatAmount = cryptoBalanceValue.fiatAmount, fiatRate = cryptoBalanceValue.fiatRate, diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/amount/AmountChangeStateTransformer.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/amount/AmountChangeStateTransformer.kt index cd74d47116..41fdc707cf 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/amount/AmountChangeStateTransformer.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/amount/AmountChangeStateTransformer.kt @@ -2,7 +2,7 @@ package com.tangem.features.staking.impl.presentation.state.transformers.amount import com.tangem.common.ui.amountScreen.converters.MaxEnterAmountConverter import com.tangem.common.ui.amountScreen.converters.field.AmountFieldChangeTransformer -import com.tangem.common.ui.amountScreen.models.MaxEnterAmount +import com.tangem.common.ui.amountScreen.models.EnterAmountBoundary import com.tangem.domain.staking.model.stakekit.Yield import com.tangem.domain.staking.model.stakekit.action.StakingActionCommonType import com.tangem.domain.tokens.model.CryptoCurrencyStatus @@ -11,6 +11,7 @@ import com.tangem.utils.transformer.Transformer internal class AmountChangeStateTransformer( private val cryptoCurrencyStatus: CryptoCurrencyStatus, + private val minimumTransactionAmount: EnterAmountBoundary?, private val value: String, private val yield: Yield, ) : Transformer { @@ -20,7 +21,7 @@ internal class AmountChangeStateTransformer( override fun transform(prevState: StakingUiState): StakingUiState { val actionType = prevState.actionType val maxEnterAmount = if (actionType == StakingActionCommonType.Exit) { - MaxEnterAmount( + EnterAmountBoundary( amount = prevState.balanceState?.cryptoAmount, fiatAmount = prevState.balanceState?.fiatAmount, fiatRate = cryptoCurrencyStatus.value.fiatRate, @@ -29,7 +30,12 @@ internal class AmountChangeStateTransformer( maxEnterAmountConverter.convert(cryptoCurrencyStatus) } - val updatedAmountState = AmountFieldChangeTransformer(maxEnterAmount, value).transform(prevState.amountState) + val updatedAmountState = AmountFieldChangeTransformer( + cryptoCurrencyStatus = cryptoCurrencyStatus, + maxEnterAmount = maxEnterAmount, + minimumTransactionAmount = minimumTransactionAmount, + value = value, + ).transform(prevState.amountState) return prevState.copy( amountState = AmountRequirementStateTransformer( diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/amount/AmountMaxValueStateTransformer.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/amount/AmountMaxValueStateTransformer.kt index 034398c337..db2865b4ec 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/amount/AmountMaxValueStateTransformer.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/amount/AmountMaxValueStateTransformer.kt @@ -2,7 +2,7 @@ package com.tangem.features.staking.impl.presentation.state.transformers.amount import com.tangem.common.ui.amountScreen.converters.MaxEnterAmountConverter import com.tangem.common.ui.amountScreen.converters.field.AmountFieldSetMaxAmountTransformer -import com.tangem.common.ui.amountScreen.models.MaxEnterAmount +import com.tangem.common.ui.amountScreen.models.EnterAmountBoundary import com.tangem.domain.staking.model.stakekit.Yield import com.tangem.domain.staking.model.stakekit.action.StakingActionCommonType import com.tangem.domain.tokens.model.CryptoCurrencyStatus @@ -11,6 +11,7 @@ import com.tangem.utils.transformer.Transformer internal class AmountMaxValueStateTransformer( private val cryptoCurrencyStatus: CryptoCurrencyStatus, + private val minimumTransactionAmount: EnterAmountBoundary?, private val actionType: StakingActionCommonType, private val yield: Yield, ) : Transformer { @@ -19,7 +20,7 @@ internal class AmountMaxValueStateTransformer( override fun transform(prevState: StakingUiState): StakingUiState { val maxEnterAmount = if (actionType == StakingActionCommonType.Exit) { - MaxEnterAmount( + EnterAmountBoundary( amount = prevState.balanceState?.cryptoAmount, fiatAmount = prevState.balanceState?.fiatAmount, fiatRate = cryptoCurrencyStatus.value.fiatRate, @@ -28,8 +29,11 @@ internal class AmountMaxValueStateTransformer( maxEnterAmountConverter.convert(cryptoCurrencyStatus) } - val updatedAmountState = AmountFieldSetMaxAmountTransformer(maxEnterAmount) - .transform(prevState.amountState) + val updatedAmountState = AmountFieldSetMaxAmountTransformer( + cryptoCurrencyStatus = cryptoCurrencyStatus, + maxAmount = maxEnterAmount, + minAmount = minimumTransactionAmount, + ).transform(prevState.amountState) return prevState.copy( amountState = AmountRequirementStateTransformer( cryptoCurrencyStatus = cryptoCurrencyStatus, diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/amount/AmountReduceByStateTransformer.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/amount/AmountReduceByStateTransformer.kt index 91468a21bd..f5e92f66ec 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/amount/AmountReduceByStateTransformer.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/amount/AmountReduceByStateTransformer.kt @@ -2,18 +2,24 @@ package com.tangem.features.staking.impl.presentation.state.transformers.amount import com.tangem.common.ui.amountScreen.converters.AmountReduceByTransformer import com.tangem.common.ui.amountScreen.converters.AmountReduceByTransformer.ReduceByData +import com.tangem.common.ui.amountScreen.models.EnterAmountBoundary import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.features.staking.impl.presentation.state.StakingUiState import com.tangem.utils.transformer.Transformer internal class AmountReduceByStateTransformer( private val cryptoCurrencyStatus: CryptoCurrencyStatus, + private val minimumTransactionAmount: EnterAmountBoundary?, private val value: ReduceByData, ) : Transformer { override fun transform(prevState: StakingUiState): StakingUiState { return prevState.copy( - amountState = AmountReduceByTransformer(cryptoCurrencyStatus, value).transform(prevState.amountState), + amountState = AmountReduceByTransformer( + cryptoCurrencyStatus = cryptoCurrencyStatus, + minimumTransactionAmount = minimumTransactionAmount, + value = value, + ).transform(prevState.amountState), ) } } \ No newline at end of file diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/amount/AmountReduceToStateTransformer.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/amount/AmountReduceToStateTransformer.kt index b826aae59e..5f7b5e5e57 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/amount/AmountReduceToStateTransformer.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/amount/AmountReduceToStateTransformer.kt @@ -1,6 +1,7 @@ package com.tangem.features.staking.impl.presentation.state.transformers.amount import com.tangem.common.ui.amountScreen.converters.AmountReduceToTransformer +import com.tangem.common.ui.amountScreen.models.EnterAmountBoundary import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.features.staking.impl.presentation.state.StakingUiState import com.tangem.utils.transformer.Transformer @@ -8,11 +9,16 @@ import java.math.BigDecimal internal class AmountReduceToStateTransformer( private val cryptoCurrencyStatus: CryptoCurrencyStatus, + private val minimumTransactionAmount: EnterAmountBoundary?, private val value: BigDecimal, ) : Transformer { override fun transform(prevState: StakingUiState): StakingUiState { return prevState.copy( - amountState = AmountReduceToTransformer(cryptoCurrencyStatus, value).transform(prevState.amountState), + amountState = AmountReduceToTransformer( + cryptoCurrencyStatus = cryptoCurrencyStatus, + minimumTransactionAmount = minimumTransactionAmount, + value = value, + ).transform(prevState.amountState), ) } } \ No newline at end of file diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/viewmodel/StakingViewModel.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/viewmodel/StakingViewModel.kt index f8dff3cf4a..e958be8c31 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/viewmodel/StakingViewModel.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/viewmodel/StakingViewModel.kt @@ -11,6 +11,7 @@ import com.tangem.common.routing.AppRoute import com.tangem.common.routing.bundle.unbundle import com.tangem.common.ui.amountScreen.converters.AmountReduceByTransformer import com.tangem.common.ui.amountScreen.models.AmountState +import com.tangem.common.ui.amountScreen.models.EnterAmountBoundary import com.tangem.common.ui.bottomsheet.permission.state.ApproveType import com.tangem.common.ui.bottomsheet.permission.state.GiveTxPermissionBottomSheetConfig import com.tangem.common.ui.notifications.NotificationUM @@ -72,6 +73,7 @@ import com.tangem.features.staking.impl.presentation.state.utils.withStubUnstake import com.tangem.utils.Provider import com.tangem.utils.coroutines.* import com.tangem.utils.extensions.isSingleItem +import com.tangem.utils.extensions.orZero import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf @@ -92,6 +94,7 @@ internal class StakingViewModel @Inject constructor( private val getBalanceHidingSettingsUseCase: GetBalanceHidingSettingsUseCase, private val getCurrencyStatusUpdatesUseCase: GetCurrencyStatusUpdatesUseCase, private val getFeePaidCryptoCurrencyStatusSyncUseCase: GetFeePaidCryptoCurrencyStatusSyncUseCase, + private val getMinimumTransactionAmountSyncUseCase: GetMinimumTransactionAmountSyncUseCase, private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase, private val getUserWalletUseCase: GetUserWalletUseCase, private val sendTransactionUseCase: SendTransactionUseCase, @@ -139,6 +142,7 @@ internal class StakingViewModel @Inject constructor( private var cryptoCurrencyStatus: CryptoCurrencyStatus by Delegates.notNull() private var processingActions: List = emptyList() private var feeCryptoCurrencyStatus: CryptoCurrencyStatus? = null + private var minimumTransactionAmount: EnterAmountBoundary? = null private var innerRouter: InnerStakingRouter by Delegates.notNull() private var userWallet: UserWallet by Delegates.notNull() @@ -366,7 +370,14 @@ internal class StakingViewModel @Inject constructor( } override fun onAmountValueChange(value: String) { - stateController.update(AmountChangeStateTransformer(cryptoCurrencyStatus, value, yield)) + stateController.update( + AmountChangeStateTransformer( + cryptoCurrencyStatus = cryptoCurrencyStatus, + minimumTransactionAmount = minimumTransactionAmount, + value = value, + yield = yield, + ), + ) } override fun onAmountPasteTriggerDismiss() { @@ -378,6 +389,7 @@ internal class StakingViewModel @Inject constructor( stateController.update( AmountMaxValueStateTransformer( cryptoCurrencyStatus = cryptoCurrencyStatus, + minimumTransactionAmount = minimumTransactionAmount, actionType = uiState.value.actionType, yield = yield, ), @@ -621,6 +633,7 @@ internal class StakingViewModel @Inject constructor( ) { AmountReduceByStateTransformer( cryptoCurrencyStatus = cryptoCurrencyStatus, + minimumTransactionAmount = minimumTransactionAmount, value = AmountReduceByTransformer.ReduceByData( reduceAmountBy = reduceAmountBy, reduceAmountByDiff = reduceAmountByDiff, @@ -633,6 +646,7 @@ internal class StakingViewModel @Inject constructor( stateController.update( AmountReduceToStateTransformer( cryptoCurrencyStatus = cryptoCurrencyStatus, + minimumTransactionAmount = minimumTransactionAmount, value = reduceAmountTo, ), ) @@ -796,6 +810,13 @@ internal class StakingViewModel @Inject constructor( feeCryptoCurrencyStatus = getFeePaidCryptoCurrencyStatusSyncUseCase(userWalletId, status).getOrNull() + minimumTransactionAmount = + getMinimumTransactionAmountSyncUseCase(userWalletId, status).getOrNull()?.let { + EnterAmountBoundary( + amount = it, + fiatRate = status.value.fiatRate.orZero(), + ) + } cryptoCurrencyStatus = status setupApprovalNeeded() @@ -929,6 +950,7 @@ internal class StakingViewModel @Inject constructor( AmountChangeStateTransformer( cryptoCurrencyStatus = cryptoCurrencyStatus, value = amountValue, + minimumTransactionAmount = minimumTransactionAmount, yield = yield, ), ) From cb9d1fc4631d89455c7fafa536c237e11c6c5697 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 6 Nov 2024 16:43:47 +0200 Subject: [PATCH 091/106] Updated on 2026-08-14 --- .../impl/presentation/viewmodel/StakingViewModel.kt | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/viewmodel/StakingViewModel.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/viewmodel/StakingViewModel.kt index e958be8c31..bb47449575 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/viewmodel/StakingViewModel.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/viewmodel/StakingViewModel.kt @@ -228,6 +228,18 @@ internal class StakingViewModel @Inject constructor( } override fun onNextClick(balanceState: BalanceState?) { + if (value.currentStep == StakingStep.InitialInfo && balanceState == null) { + stateController.update( + SetConfirmationStateInitTransformer( + isEnter = true, + isExplicitExit = false, + balanceState = null, + cryptoCurrencyStatus = cryptoCurrencyStatus, + stakingApproval = stakingApproval, + stakingAllowance = stakingAllowance, + ), + ) + } stakingStateRouter.onNextClick() } From 1232de0a81fb18213d18cd54e5e8095aee5a574c Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 6 Nov 2024 20:52:15 +0500 Subject: [PATCH 092/106] Updated on 2026-08-14 --- .../notifications/StakingInfoNotificationsFactory.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/notifications/StakingInfoNotificationsFactory.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/notifications/StakingInfoNotificationsFactory.kt index 36f7acec1c..11b61e92b9 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/notifications/StakingInfoNotificationsFactory.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/notifications/StakingInfoNotificationsFactory.kt @@ -171,7 +171,7 @@ internal class StakingInfoNotificationsFactory( val exitRequirements = yield.args.exit?.args?.get(Yield.Args.ArgType.AMOUNT) ?: return val amountLeft = maxAmount - actionAmount - val isNotEnoughLeft = !amountLeft.isZero() && amountLeft < exitRequirements.minimum + val isNotEnoughLeft = !amountLeft.isZero() && amountLeft < exitRequirements.minimum.orZero() if (exitRequirements.required && isNotEnoughLeft) { add(StakingNotification.Warning.LowStakedBalance) From 0d30d25bad7dcfb56c8f44c55ca15b172c593a6c Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 7 Nov 2024 10:57:09 +0200 Subject: [PATCH 093/106] Updated on 2026-08-14 --- .../InvalidatePendingTransactionsUseCase.kt | 28 +++++++++++++++---- 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/domain/staking/src/main/java/com/tangem/domain/staking/InvalidatePendingTransactionsUseCase.kt b/domain/staking/src/main/java/com/tangem/domain/staking/InvalidatePendingTransactionsUseCase.kt index dbb7c578d4..ce0b576bab 100644 --- a/domain/staking/src/main/java/com/tangem/domain/staking/InvalidatePendingTransactionsUseCase.kt +++ b/domain/staking/src/main/java/com/tangem/domain/staking/InvalidatePendingTransactionsUseCase.kt @@ -37,8 +37,12 @@ class InvalidatePendingTransactionsUseCase( processingActions.forEach { action -> when (action.type) { - StakingActionType.STAKE, StakingActionType.VOTE, StakingActionType.VOTE_LOCKED -> { - processEnterAction(balances, action) + StakingActionType.STAKE, StakingActionType.VOTE -> { + addStubStakedPendingTransaction(balances, action) + } + StakingActionType.VOTE_LOCKED -> { + addStubStakedPendingTransaction(balances, action) + removeLockedBalance(balances, action) } StakingActionType.WITHDRAW -> { modifyBalancesByStatus(balances, action, BalanceType.UNSTAKED) @@ -58,7 +62,15 @@ class InvalidatePendingTransactionsUseCase( return balances } - private fun processEnterAction(balances: MutableList, action: StakingAction) { + private fun removeLockedBalance(balances: MutableList, action: StakingAction) { + val index = findBalanceIndex(balances, action, BalanceType.LOCKED) + + if (index != -1) { + balances.removeAt(index) + } + } + + private fun addStubStakedPendingTransaction(balances: MutableList, action: StakingAction) { balances.add( BalanceItem( groupId = UUID.randomUUID().toString(), @@ -75,12 +87,16 @@ class InvalidatePendingTransactionsUseCase( } private fun modifyBalancesByStatus(balances: MutableList, action: StakingAction, type: BalanceType) { - val index = balances.indexOfFirst { - !it.isPending && it.amount isEqualTo action.amount && it.type == type - } + val index = findBalanceIndex(balances, action, type) if (index != -1) { balances[index] = balances[index].copy(isPending = true) } } + + private fun findBalanceIndex(balances: MutableList, action: StakingAction, type: BalanceType): Int { + return balances.indexOfFirst { + !it.isPending && it.amount isEqualTo action.amount && it.type == type + } + } } \ No newline at end of file From 2892c0b6a8e599cbaeb12b2af5a5ea52da2bc2cf Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 7 Nov 2024 10:57:19 +0200 Subject: [PATCH 094/106] Updated on 2026-08-14 --- .../analytics/utils/StakingAnalyticSender.kt | 2 +- .../state/InnerYieldBalanceState.kt | 2 +- .../converters/YieldBalancesConverter.kt | 2 +- .../previewdata/InitialStakingStatePreview.kt | 2 +- .../ui/StakingInitialInfoContent.kt | 30 +++++---- .../TokenDetailsLoadedBalanceConverter.kt | 66 +++++++++---------- .../state/factory/TokenDetailsStateFactory.kt | 3 - .../viewmodels/TokenDetailsViewModel.kt | 3 - 8 files changed, 51 insertions(+), 59 deletions(-) diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/analytics/utils/StakingAnalyticSender.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/analytics/utils/StakingAnalyticSender.kt index 114d86d579..5decf65514 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/analytics/utils/StakingAnalyticSender.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/analytics/utils/StakingAnalyticSender.kt @@ -18,7 +18,7 @@ internal class StakingAnalyticSender( fun initialInfoScreen(value: StakingUiState) { val initialInfoState = value.initialInfoState as? StakingStates.InitialInfoState.Data val validatorState = initialInfoState?.yieldBalance as? InnerYieldBalanceState.Data - val validatorCount = validatorState?.balance + val validatorCount = validatorState?.balances ?.filterNot { it.validator?.address.isNullOrBlank() } ?.distinctBy { it.validator?.address } ?.size ?: 0 diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/InnerYieldBalanceState.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/InnerYieldBalanceState.kt index 7fa7d42377..94cd29f646 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/InnerYieldBalanceState.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/InnerYieldBalanceState.kt @@ -16,7 +16,7 @@ internal sealed class InnerYieldBalanceState { val rewardsFiat: String, val rewardBlockType: RewardBlockType, val isActionable: Boolean, - val balance: ImmutableList, + val balances: ImmutableList, ) : InnerYieldBalanceState() data object Empty : InnerYieldBalanceState() diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/converters/YieldBalancesConverter.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/converters/YieldBalancesConverter.kt index 29ccbc1d29..41c9881df8 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/converters/YieldBalancesConverter.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/converters/YieldBalancesConverter.kt @@ -53,7 +53,7 @@ internal class YieldBalancesConverter( }, rewardBlockType = type, isActionable = isActionable, - balance = balanceToShowItems.mapBalances(), + balances = balanceToShowItems.mapBalances(), ) } else { InnerYieldBalanceState.Empty diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/previewdata/InitialStakingStatePreview.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/previewdata/InitialStakingStatePreview.kt index 293ba68c01..9bc454ab92 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/previewdata/InitialStakingStatePreview.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/previewdata/InitialStakingStatePreview.kt @@ -68,7 +68,7 @@ internal object InitialStakingStatePreview { rewardsCrypto = "100 SOL", rewardBlockType = RewardBlockType.RewardUnavailable, isActionable = true, - balance = persistentListOf( + balances = persistentListOf( BalanceState( groupId = "groupId", title = stringReference("Binance"), diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingInitialInfoContent.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingInitialInfoContent.kt index d0d364184d..1fbbff9df2 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingInitialInfoContent.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingInitialInfoContent.kt @@ -128,18 +128,20 @@ private fun LazyListScope.activeStakingBlock( clickIntents: StakingClickIntents, isBalanceHidden: Boolean, ) { - val balances = (state.yieldBalance as? InnerYieldBalanceState.Data)?.balance - if (!balances.isNullOrEmpty()) { - item(key = STAKING_REWARD_BLOCK_KEY) { - Column(modifier = Modifier.animateItem()) { - StakingRewardBlock( - yieldBalanceState = state.yieldBalance, - onRewardsClick = clickIntents::openRewardsValidators, - isBalanceHidden = isBalanceHidden, - ) - SpacerH12() - } + val innerYieldBalanceState = state.yieldBalance as? InnerYieldBalanceState.Data ?: return + + item(key = STAKING_REWARD_BLOCK_KEY) { + Column(modifier = Modifier.animateItem()) { + StakingRewardBlock( + yieldBalanceState = state.yieldBalance, + onRewardsClick = clickIntents::openRewardsValidators, + isBalanceHidden = isBalanceHidden, + ) + SpacerH12() } + } + + if (innerYieldBalanceState.balances.isNotEmpty()) { item(ACTIVE_STAKING_BLOCK_KEY) { Text( text = stringResource(id = R.string.staking_your_stakes), @@ -148,7 +150,7 @@ private fun LazyListScope.activeStakingBlock( modifier = Modifier .roundedShapeItemDecoration( currentIndex = 0, - lastIndex = 1 + state.yieldBalance.balance.lastIndex, + lastIndex = 1 + state.yieldBalance.balances.lastIndex, addDefaultPadding = false, ) .fillMaxWidth() @@ -162,7 +164,7 @@ private fun LazyListScope.activeStakingBlock( ) } itemsIndexed( - items = state.yieldBalance.balance, + items = state.yieldBalance.balances, key = { _, balance -> // Staked balance does not have unique identifier. balance.toString() @@ -177,7 +179,7 @@ private fun LazyListScope.activeStakingBlock( .animateItem() .roundedShapeItemDecoration( currentIndex = index + 1, - lastIndex = state.yieldBalance.balance.lastIndex + 1, + lastIndex = state.yieldBalance.balances.lastIndex + 1, addDefaultPadding = false, ), ) diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsLoadedBalanceConverter.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsLoadedBalanceConverter.kt index 676cff23f2..e9d2706803 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsLoadedBalanceConverter.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsLoadedBalanceConverter.kt @@ -14,7 +14,6 @@ import com.tangem.core.ui.format.bigdecimal.* import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.staking.model.StakingAvailability import com.tangem.domain.staking.model.StakingEntryInfo -import com.tangem.domain.staking.model.stakekit.BalanceItem import com.tangem.domain.staking.model.stakekit.RewardBlockType import com.tangem.domain.staking.model.stakekit.YieldBalance import com.tangem.domain.tokens.error.CurrencyStatusError @@ -41,7 +40,6 @@ internal class TokenDetailsLoadedBalanceConverter( private val currentStateProvider: Provider, private val appCurrencyProvider: Provider, private val stakingEntryInfoProvider: Provider, - private val pendingBalancesProvider: Provider>, private val stakingAvailabilityProvider: Provider, private val symbol: String, private val decimals: Int, @@ -143,53 +141,51 @@ internal class TokenDetailsLoadedBalanceConverter( } private fun getYieldBalance(status: CryptoCurrencyStatus, state: TokenDetailsState): StakingBlockUM? { + return when (stakingAvailabilityProvider.invoke()) { + StakingAvailability.TemporaryUnavailable -> StakingBlockUM.TemporaryUnavailable + StakingAvailability.Unavailable -> null + is StakingAvailability.Available -> getStakingInfoBlock(status, state) + } + } + + private fun getStakingInfoBlock(status: CryptoCurrencyStatus, state: TokenDetailsState): StakingBlockUM? { val yieldBalance = status.value.yieldBalance as? YieldBalance.Data + val stakingCryptoAmount = yieldBalance?.getTotalStakingBalance() + val pendingBalances = yieldBalance?.balance?.items ?: emptyList() val stakingEntryInfo = stakingEntryInfoProvider.invoke() - val stakingAvailability = stakingAvailabilityProvider.invoke() val iconState = state.tokenInfoBlockState.iconState - val pendingBalances = pendingBalancesProvider.invoke() - val fiatRate = status.value.fiatRate return when { - stakingAvailability == StakingAvailability.TemporaryUnavailable -> { - StakingBlockUM.TemporaryUnavailable - } - stakingAvailability == StakingAvailability.Unavailable -> { - null - } - stakingCryptoAmount.isNullOrZero() && stakingEntryInfo != null && pendingBalances.isEmpty() -> { - getStakeAvailableState(stakingEntryInfo, iconState) - } - stakingCryptoAmount.isNullOrZero() && stakingEntryInfo != null && pendingBalances.isNotEmpty() -> { - val pendingBalancesCryptoAmount = pendingBalances.sumOf { it.amount } - - val stakingFiatAmount = fiatRate?.multiply(pendingBalancesCryptoAmount) - getStakedState( - status = status, - stakingCryptoAmount = pendingBalancesCryptoAmount, - stakingFiatAmount = stakingFiatAmount, - stakingRewardAmount = null, - ) + stakingCryptoAmount.isNullOrZero() && stakingEntryInfo != null -> { + if (pendingBalances.isEmpty()) { + getStakeAvailableState(stakingEntryInfo, iconState) + } else { + getStakedBlockWithFiatAmount(status, pendingBalances.sumOf { it.amount }, null) + } } stakingCryptoAmount.isNullOrZero() && stakingEntryInfo == null -> { null } - else -> { - val stakingRewardAmount = yieldBalance?.getRewardStakingBalance()?.let { fiatRate?.multiply(it) } - val stakingFiatAmount = stakingCryptoAmount?.let { fiatRate?.multiply(it) } - - getStakedState( - status = status, - stakingCryptoAmount = stakingCryptoAmount, - stakingFiatAmount = stakingFiatAmount, - stakingRewardAmount = stakingRewardAmount, - ) - } + else -> getStakedBlockWithFiatAmount(status, stakingCryptoAmount, yieldBalance?.getRewardStakingBalance()) } } + private fun getStakedBlockWithFiatAmount( + status: CryptoCurrencyStatus, + stakingAmount: BigDecimal?, + rewardAmount: BigDecimal?, + ): StakingBlockUM.Staked { + val fiatRate = status.value.fiatRate + return getStakedState( + status = status, + stakingCryptoAmount = stakingAmount, + stakingFiatAmount = stakingAmount?.let { fiatRate?.multiply(it) }, + stakingRewardAmount = rewardAmount?.let { fiatRate?.multiply(it) }, + ) + } + private fun getMarketPriceState( status: CryptoCurrencyStatus.Value, currencySymbol: String, diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsStateFactory.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsStateFactory.kt index 80f8e8e1dc..08c2f45190 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsStateFactory.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsStateFactory.kt @@ -21,7 +21,6 @@ import com.tangem.domain.staking.GetStakingAvailabilityUseCase import com.tangem.domain.staking.GetStakingIntegrationIdUseCase import com.tangem.domain.staking.model.StakingAvailability import com.tangem.domain.staking.model.StakingEntryInfo -import com.tangem.domain.staking.model.stakekit.BalanceItem import com.tangem.domain.tokens.error.CurrencyStatusError import com.tangem.domain.tokens.model.* import com.tangem.domain.tokens.model.warnings.CryptoCurrencyWarning @@ -54,7 +53,6 @@ internal class TokenDetailsStateFactory( private val stakingEntryInfoProvider: Provider, private val stakingAvailabilityProvider: Provider, private val cryptoCurrencyStatusProvider: Provider, - private val pendingBalancesProvider: Provider>, private val clickIntents: TokenDetailsClickIntents, private val networkHasDerivationUseCase: NetworkHasDerivationUseCase, private val getUserWalletUseCase: GetUserWalletUseCase, @@ -87,7 +85,6 @@ internal class TokenDetailsStateFactory( appCurrencyProvider = appCurrencyProvider, stakingEntryInfoProvider = stakingEntryInfoProvider, stakingAvailabilityProvider = stakingAvailabilityProvider, - pendingBalancesProvider = pendingBalancesProvider, symbol = symbol, decimals = decimals, clickIntents = clickIntents, diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt index 5f5e518a6c..1427b63a24 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt @@ -38,7 +38,6 @@ import com.tangem.domain.staking.GetStakingIntegrationIdUseCase import com.tangem.domain.staking.GetYieldUseCase import com.tangem.domain.staking.model.StakingAvailability import com.tangem.domain.staking.model.StakingEntryInfo -import com.tangem.domain.staking.model.stakekit.BalanceItem import com.tangem.domain.tokens.* import com.tangem.domain.tokens.legacy.TradeCryptoAction import com.tangem.domain.tokens.legacy.TradeCryptoAction.TransactionInfo @@ -149,7 +148,6 @@ internal class TokenDetailsViewModel @Inject constructor( private val warningsJobHolder = JobHolder() private val swapTxJobHolder = JobHolder() private val selectedAppCurrencyFlow: StateFlow = createSelectedAppCurrencyFlow() - private val stakingPendingBalances: List = emptyList() // TODO staking private var cryptoCurrencyStatus: CryptoCurrencyStatus? = null private var stakingEntryInfo: StakingEntryInfo? = null @@ -162,7 +160,6 @@ internal class TokenDetailsViewModel @Inject constructor( stakingEntryInfoProvider = Provider { stakingEntryInfo }, stakingAvailabilityProvider = Provider { stakingAvailability }, cryptoCurrencyStatusProvider = Provider { cryptoCurrencyStatus }, - pendingBalancesProvider = Provider { stakingPendingBalances }, clickIntents = this, networkHasDerivationUseCase = networkHasDerivationUseCase, getUserWalletUseCase = getUserWalletUseCase, From 58927597e9795d4c590980918ec5fc10801eb51a Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 7 Nov 2024 17:11:53 +0500 Subject: [PATCH 095/106] Updated on 2026-08-14 --- .../tangem/common/ui/notifications/NotificationsFactory.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/common/ui/src/main/java/com/tangem/common/ui/notifications/NotificationsFactory.kt b/common/ui/src/main/java/com/tangem/common/ui/notifications/NotificationsFactory.kt index 359d6dd9c0..13b0c42b88 100644 --- a/common/ui/src/main/java/com/tangem/common/ui/notifications/NotificationsFactory.kt +++ b/common/ui/src/main/java/com/tangem/common/ui/notifications/NotificationsFactory.kt @@ -193,7 +193,7 @@ object NotificationsFactory { if (isExceedsLimit) { add( NotificationUM.Error.MinimumAmountError( - amount = dustValue.parseBigDecimal(cryptoCurrencyStatus.currency.decimals), + amount = dustValue.format { crypto(cryptoCurrencyStatus.currency) }, ), ) } @@ -301,7 +301,7 @@ object NotificationsFactory { dustValue?.let { add( NotificationUM.Error.MinimumAmountError( - amount = it.parseBigDecimal(sendingCurrency.decimals), + amount = it.format { crypto(sendingCurrency) }, ), ) } From defc168c786e23f809233147dd66f6dfb55bc113 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 7 Nov 2024 11:24:53 +0500 Subject: [PATCH 096/106] Updated on 2026-08-14 --- app/src/main/java/com/tangem/tap/features/main/MainViewModel.kt | 2 -- .../src/main/java/com/tangem/core/analytics/models/Basic.kt | 2 -- 2 files changed, 4 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/features/main/MainViewModel.kt b/app/src/main/java/com/tangem/tap/features/main/MainViewModel.kt index e9db3ee67e..444b2c6f89 100644 --- a/app/src/main/java/com/tangem/tap/features/main/MainViewModel.kt +++ b/app/src/main/java/com/tangem/tap/features/main/MainViewModel.kt @@ -6,7 +6,6 @@ import com.tangem.blockchainsdk.BlockchainSDKFactory import com.tangem.common.routing.AppRoute import com.tangem.common.routing.AppRouter import com.tangem.core.analytics.Analytics -import com.tangem.core.analytics.models.Basic import com.tangem.datasource.api.common.config.managers.ApiConfigsManager import com.tangem.domain.appcurrency.FetchAppCurrenciesUseCase import com.tangem.domain.balancehiding.BalanceHidingSettings @@ -103,7 +102,6 @@ internal class MainViewModel @Inject constructor( .distinctUntilChanged() .onEach { userWallet -> Analytics.setContext(userWallet.scanResponse) - Analytics.send(Basic.WalletOpened()) } .flowOn(dispatchers.io) .launchIn(viewModelScope) diff --git a/core/analytics/models/src/main/java/com/tangem/core/analytics/models/Basic.kt b/core/analytics/models/src/main/java/com/tangem/core/analytics/models/Basic.kt index 3c1d48f13b..c623358a84 100644 --- a/core/analytics/models/src/main/java/com/tangem/core/analytics/models/Basic.kt +++ b/core/analytics/models/src/main/java/com/tangem/core/analytics/models/Basic.kt @@ -78,8 +78,6 @@ sealed class Basic( error = error, ) - class WalletOpened : Basic(event = "Wallet Opened") - class ButtonSupport(source: AnalyticsParam.ScreensSources) : Basic( event = "Request Support", params = mapOf( From 32d37fd571adc38f79405b0b62b7a30bbc134ff2 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 7 Nov 2024 17:21:50 +0500 Subject: [PATCH 097/106] Updated on 2026-08-14 --- .../java/com/tangem/common/ui/amountScreen/ui/AmountField.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/ui/src/main/java/com/tangem/common/ui/amountScreen/ui/AmountField.kt b/common/ui/src/main/java/com/tangem/common/ui/amountScreen/ui/AmountField.kt index c74f183aa6..f2f8e078f9 100644 --- a/common/ui/src/main/java/com/tangem/common/ui/amountScreen/ui/AmountField.kt +++ b/common/ui/src/main/java/com/tangem/common/ui/amountScreen/ui/AmountField.kt @@ -138,7 +138,7 @@ private fun AmountFieldError( exit = fadeOut(), modifier = modifier, ) { - val errorText = remember(this) { error } + val errorText = remember(this, error) { error } val color = if (isError) TangemTheme.colors.text.warning else TangemTheme.colors.text.attention Text( text = errorText.resolveReference(), From cc81fb6d522b835b225ec78f173d36c4f9cf673a Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 8 Nov 2024 12:59:31 +0200 Subject: [PATCH 098/106] Updated on 2026-08-14 --- .../domain/staking/InvalidatePendingTransactionsUseCase.kt | 2 +- .../impl/presentation/viewmodel/StakingViewModel.kt | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/domain/staking/src/main/java/com/tangem/domain/staking/InvalidatePendingTransactionsUseCase.kt b/domain/staking/src/main/java/com/tangem/domain/staking/InvalidatePendingTransactionsUseCase.kt index ce0b576bab..eb07d96ab9 100644 --- a/domain/staking/src/main/java/com/tangem/domain/staking/InvalidatePendingTransactionsUseCase.kt +++ b/domain/staking/src/main/java/com/tangem/domain/staking/InvalidatePendingTransactionsUseCase.kt @@ -50,7 +50,7 @@ class InvalidatePendingTransactionsUseCase( StakingActionType.UNLOCK_LOCKED -> { modifyBalancesByStatus(balances, action, BalanceType.LOCKED) } - StakingActionType.UNSTAKE -> { + StakingActionType.UNSTAKE, StakingActionType.RESTAKE -> { modifyBalancesByStatus(balances, action, BalanceType.STAKED) } else -> { diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/viewmodel/StakingViewModel.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/viewmodel/StakingViewModel.kt index bb47449575..522d616f07 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/viewmodel/StakingViewModel.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/viewmodel/StakingViewModel.kt @@ -50,6 +50,7 @@ import com.tangem.domain.wallets.usecase.GetUserWalletUseCase import com.tangem.domain.staking.analytics.StakeScreenSource import com.tangem.domain.staking.analytics.StakingAnalyticsEvent import com.tangem.domain.staking.model.stakekit.* +import com.tangem.domain.staking.model.stakekit.action.StakingActionType import com.tangem.features.staking.impl.analytics.StakingParamsInterceptor import com.tangem.features.staking.impl.analytics.utils.StakingAnalyticSender import com.tangem.features.staking.impl.navigation.InnerStakingRouter @@ -941,7 +942,7 @@ internal class StakingViewModel @Inject constructor( stateController.updateAll( SetConfirmationStateInitTransformer( isEnter = false, - isExplicitExit = balanceType == BalanceType.STAKED, + isExplicitExit = isExplicitExit(balanceType, pendingAction), balanceState = balanceState, cryptoCurrencyStatus = cryptoCurrencyStatus, stakingApproval = stakingApproval, @@ -968,6 +969,10 @@ internal class StakingViewModel @Inject constructor( ) } + private fun isExplicitExit(balanceType: BalanceType, pendingAction: PendingAction?): Boolean { + return balanceType == BalanceType.STAKED && pendingAction?.type != StakingActionType.RESTAKE + } + private fun isAssentState(): Boolean { return value.currentStep == StakingStep.Confirmation && (value.confirmationState as? StakingStates.ConfirmationState.Data)?.innerState == From 3af77d53b89c9eea7c5f3369aac16c564989dc1b Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 11 Nov 2024 14:01:49 +0500 Subject: [PATCH 099/106] Updated on 2026-08-14 --- core/res/src/main/res/values-de/strings.xml | 30 ++++++++++++++++--- core/res/src/main/res/values-es/strings.xml | 26 +++++++++++++++- core/res/src/main/res/values-fr/strings.xml | 24 +++++++++++++++ core/res/src/main/res/values-ja/strings.xml | 25 +++++++++++++++- core/res/src/main/res/values-ru/strings.xml | 15 ++++++++-- .../src/main/res/values-uk-rUA/strings.xml | 26 ++++++++++++++-- core/res/src/main/res/values/strings.xml | 18 +++++++++-- 7 files changed, 152 insertions(+), 12 deletions(-) diff --git a/core/res/src/main/res/values-de/strings.xml b/core/res/src/main/res/values-de/strings.xml index ac5448db47..db671f2128 100644 --- a/core/res/src/main/res/values-de/strings.xml +++ b/core/res/src/main/res/values-de/strings.xml @@ -1,5 +1,11 @@ + Token nicht in Deinem Portfolio gefunden? Überprüfe die Märkte, um es zu finden und zum Kauf hinzuzufügen + Token nicht in Deinem Portfolio gefunden? Überprüfe die Märkte, um es zu finden und zum Verkauf hinzuzufügen + Wähle den Token + Token nicht in Deinem Portfolio gefunden? Überprüfe die Märkte, um es für Swaps zu finden und hinzuzufügen + Es gibt keine verfügbaren Token, die mit dem ausgewählten Token getauscht werden können. Bitte wähle einen anderen. + Kein verfügbares Paar Netzwerk wählen Token anlegen Token verwalten @@ -551,10 +557,21 @@ Zugangscode wiederherstellen Identische Karten oder Ring Zugangscode + Suche nach Land + Nicht verfügbar + Suche nach Währung + Der Kaufbetrag sollte nicht höher sein als %s + Der zu kaufende Betrag muss mindestens %s betragen + Keine verfügbaren Anbieter für diese Währung Bezahlen mit + Du kannst Deine Transaktion beim Drittanbieter %s abschließen. + Umleitung auf %s... Unsere Dienstleistungen sind in diesem Land nicht verfügbar Änder oder bestätige bitte Dein Wohnsitz wurde identifiziert als + Residenz + Bitte wähle das richtige Land aus, um korrekte Zahlungsoptionen und Dienstleistungen zu gewährleisten. + Einstellungen Über Gruppe erstellen Nach Guthaben @@ -741,6 +758,8 @@ Mit dem Staking kannst Du %1$s verdienen. Deine Staking-Belohnungen erhältst Du jede Woche. Sicher staken und wöchentliche Belohnungen verdienen. Verdiene Staking-Belohnungen + Dein verbleibender Stakingbetrag ist zu niedrig, um den Einsatz aufzuheben. Du musst mehr Staken, um den Mindestbetrag zum Aufheben des Einsatzes zu erreichen. + Niedriges Stakingguthaben Aufgrund von Netzwerkproblemen ist Staking derzeit nicht verfügbar. Bitte versuche es später erneut. Beim Staking im %1$s -Netzwerk mit einem neuen Validator werden alle zuvor eingesetzten Kryptos automatisch an diesen Validator übertragen Reinvestiert Deine verdienten Prämien in Deinen Einsatzbetrag und erhöht so den potenziellen Gewinn. @@ -785,7 +804,7 @@ Lösen der Bindungen Gelocktes unlocken Entsperren - Die Anzahl der zu stakenden Krypros muss mindesten %s betragen + Der unstaking-Betrag muss mindestens %s betragen. Der Betrag übersteigt das eingesetzte Guthaben Unstaken Staking beenden @@ -815,8 +834,7 @@ Der Betrag beinhaltet:\n• Honorar des Dienstleisters\n• Netzwerkgebühr für das Senden %1$s von der Börse zurück an die Adresse des Benutzers. \n\n Provider-Slippage kann bis zu %2$s Der Betrag enthält die Gebühren des Dienstleisters. Der Betrag beinhaltet die Gebühr des Dienstleisters. \n\nProvider-Slippage kann bis zu %s - Anbieter-Slippage kann bis zu %s ausfallen. - Gebühren + Information Alle dezentralen Börsen benötigen Genehmigungen, um zu verhindern, dass intelligente Verträge ohne Ihre Erlaubnis auf Ihre Geldbörse zugreifen. Smart Contracts können nicht auf Ihre Token zugreifen, wenn Sie nicht zustimmen. Indem Sie Ihre Token \"freischalten\", ermächtigen Sie den 1-Zoll-Smart-Contract, sie auszugeben. Die Miner des Netzwerks erhalten eine (von Ihnen bezahlte) Gasgebühr, um diese Aktion in der Blockchain aufzuzeichnen. Sie können Ihre Token tauschen, nachdem Sie Ihre Zustimmung gegeben haben. Genehmigen Fehler bei der Gebührenschätzung. Bitte sende dein Feedback an den Support. @@ -854,6 +872,9 @@ Tausche diesen Token gegen einen anderen zu %1$s Servicegebühren von Februar %2$s-%3$s. Tausche mit Changelly, %s Gebühren Jetzt tauschen + Nicht verfügbar zum Kauf + Nicht zum Verkauf verfügbar + Nicht verfügbar für Tausch von %s Vertrag: %s Du hast noch keine Transaktionen Der Transaktionsverlauf konnte nicht geladen werden.\nKlicke auf die Schaltfläche Neu laden, um die Informationen zu aktualisieren. @@ -863,6 +884,7 @@ von: %s zu: %s Validierer: %s + Der Mindesttransaktionsbetrag beträgt %1$s. Versuche es erneut Du hast dieselbe Karte oder Ring gescannt. Um ein Zwillings-Wallet zu erstellen, musst du die Karte oder Ring mit der Nummer %d scannen. Du hast die falsche Doppelkarte oder Ring gescannt. Bitte versuche eine andere Karte oder Ring @@ -998,7 +1020,7 @@ Das Solana-Netz ist überlastet. Wenn deine Transaktion nicht innerhalb von 2 Minuten bearbeitet wird, wiederhole bitte die Transaktion. Solana Netzwerkalarm Das Solana-Netzwerk erhebt alle 2 Tage eine Miete von %1$s. Konten, die sich die Miete nicht leisten können, werden aus dem Netzwerk gelöscht. Hinterlege deinem Konto mit mehr als %2$s, um es kostenlos zu nutzen. - Einige Netzwerke sind derzeit nicht erreichbar. Bitte versuche es später erneut. + Wischen Sie nach unten, um zu aktualisieren, oder versuchen Sie es später erneut. Einige Netzwerke sind nicht erreichbar Dies ist eine Testnet-Karte. Sie kann keine Transaktionen verarbeiten und sollte nur zu Test- und Entwicklungszwecken verwendet werden. Nur für Testzwecke diff --git a/core/res/src/main/res/values-es/strings.xml b/core/res/src/main/res/values-es/strings.xml index ac89c7802b..8c35a676fa 100644 --- a/core/res/src/main/res/values-es/strings.xml +++ b/core/res/src/main/res/values-es/strings.xml @@ -1,5 +1,11 @@ + ¿No encuentra el token en su billetera? Consulte los mercados para encontrarlo y añadirlo a la compra + ¿No encuentra el token en su billetera? Consulte los mercados para encontrarlo y añadirlo a la venta + Elige el token + ¿No encuentras el token en tu billetera? Consulte los mercados para encontrarlo y agregarlo al intercambio + No hay tokens disponibles para intercambiar con el token seleccionado. Por favor elige otro. + No hay par disponible Elige red Agregue un token personalizado Gestionar tokens @@ -88,7 +94,7 @@ Reclamar Reclame recompensas Cerrar - confirme + Confirme Continuar Copiar Copiar la dirección @@ -551,9 +557,20 @@ Restaurar código de acceso Tarjetas idénticas Código de acceso + Buscar por país + Indisponible + Buscar por moneda + El monto de la compra no debe ser mayor a %s + La cantidad a comprar debe ser como mínimo %s + No hay proveedores disponibles para esta moneda + Pagar con Nuestros servicios no están disponibles en este país Cámbielo o confírmelo Su residencia ha sido identificada como + Residencia + Seleccione el país correcto para garantizar opciones de pago y servicios precisos. + Ajustes + Vía Agrupar Por saldo Organizar tokens @@ -696,6 +713,7 @@ Nombre El monto del staking debe ser al menos %s El monto del staking se redondeará a %1$s TRX debido a las reglas de la red. + El monto de cancelación del staking se redondeará a %1$s TRX debido a las reglas de la red. Unstaking de la reclamación Tarifa de staking de la cuenta Una cuenta de staking es una cuenta especial donde se almacenan los tokens SOL de staking. Se crea cuando delegas sus tokens a un validador para participar en la validación de transacciones y ganar recompensas. Se cobra una pequeña tarifa por crear la cuenta de staking, que se devuelve una vez que se completa el staking. @@ -738,6 +756,8 @@ El staking le permite ganar %1$s. Sus recompensas por apostar llegan todas las semanas. Haga staking de forma segura y comience a ganar recompensas semanales Gane recompensas por staking + Su saldo de staking será demasiado bajo para cancelar el staking. Necesitará hacer staking más para alcanzar la cantidad mínima para cancelarlo. + Saldo de staking bajo El staking no está disponible actualmente debido a las condiciones de la red. Inténtelo de nuevo más tarde. El staking en la red %1$s con un nuevo validador transferirá automáticamente todos los fondos previamente en staking a este validador. Reinvierta las recompensas obtenidas en el monto apostado, aumentando las ganancias potenciales. @@ -771,6 +791,7 @@ Recompensas El stake está bloqueado Hacer más staking + Monto del staking Ud hace el staking de %1$s y recibirá %2$s Toque para desbloquear Pulse para desbloquear o votar @@ -849,6 +870,9 @@ Cambie este token por otro por una tarifa de servicio de %1$s del %2$s al %3$s de febrero. Intercambio con Changelly, %s tarifa Intercambie ahora + No disponible para compra + No disponible para vender + No disponible para cambio desde %s contacto: %s Aún no tiene ninguna transacción Error al cargar el historial de transacciones.\nHaga clic en el botón de recarga para actualizar la información. diff --git a/core/res/src/main/res/values-fr/strings.xml b/core/res/src/main/res/values-fr/strings.xml index d2a3505ef9..cfb3abf10e 100644 --- a/core/res/src/main/res/values-fr/strings.xml +++ b/core/res/src/main/res/values-fr/strings.xml @@ -1,5 +1,11 @@ + Jeton non trouvé dans votre portefeuille ? Consultez les marchés pour le trouver et l\'ajouter à l\'achat + Jeton non trouvé dans votre portefeuille ? Consultez les marchés pour le trouver et l\'ajouter à la vente + Choisissez le jeton + Jeton non trouvé dans votre portefeuille ? Consultez les marchés pour le trouver et l\'ajouter à l\'échange + Il n\'y a pas de token disponible pour échanger avec le token sélectionné. Veuillez en choisir un autre. + Aucune paire disponible Choisissez le réseau Ajouter un jeton personnalisé Gérer les jetons @@ -551,9 +557,20 @@ Restauration du code d\'accès Cartes identiques Code d\'accès + Recherche par pays + Indisponible + Recherche par devise + Le montant de l\'achat ne doit pas dépasser %s + Le montant à acheter doit être au moins %s + Aucun fournisseur disponible pour cette devise + Payer avec Nos services ne sont pas disponibles dans ce pays Modifiez-le ou confirmez-le Votre résidence a été identifiée comme + Résidence + Veuillez sélectionner le bon pays pour garantir des options de paiement et des services précis. + Paramètres + Via Grouper Par solde Organiser les jetons @@ -696,6 +713,7 @@ Nom Le montant à staker doit être au moins %s Le montant du staking sera arrondi à %1$s TRX en raison des règles du réseau. + Le montant d\'annulation du staking sera arrondi à %1$s TRX en raison des règles du réseau. Réclamation déstakée Frais de staking du compte Un compte de staking est un compte spécial où sont stockés les jetons SOL stakés. Il est créé lorsque vous déléguez vos jetons à un validateur pour participer à la validation des transactions et gagner des récompenses. Des frais minimes sont facturés pour la création du compte de staking, qui sont restitués une fois le staking terminé. @@ -738,6 +756,8 @@ Le staking vous permet de gagner %1$s. Vos récompenses de staking arrivent toutes les semaines. Stakez en toute sécurité et commencez à gagner des récompenses hebdomadaires Gagnez des récompenses de staking + Votre solde stakée sera trop faible pour annuler votre staking. Vous devrez staker davantage pour atteindre le montant minimum pour annuler le staking. + Solde de staking faible L\'option de staking n\'est actuellement pas disponible en raison des conditions du réseau. Veuillez réessayer plus tard. Le staking dans le réseau %1$s avec un nouveau validateur transférera automatiquement tous les fonds précédemment stakés vers ce validateur Réinvestissez vos récompenses gagnées dans le montant que vous avez staké, augmentant ainsi vos gains potentiels. @@ -771,6 +791,7 @@ Récompenses Stake verrouillé Staker plus + Montant staké Vous stakez %1$s et recevrez %2$s Appuyez pour déverrouiller Appuyez pour déverrouiller ou voter @@ -849,6 +870,9 @@ Échangez ce jeton contre un autre moyennant des frais de service de %1$s du %2$s au %3$s février. Échangez avec Changelly, %s frais Échangez maintenant + Non disponible à l\'achat + Indisponible à la vente + Non disponible pour l\'échange de %s contrat : %s Vous n\'avez pas encore de transactions Échec du chargement de l\'historique des transactions.\nCliquez sur le bouton de rechargement pour mettre à jour les informations. diff --git a/core/res/src/main/res/values-ja/strings.xml b/core/res/src/main/res/values-ja/strings.xml index b2998a24d0..58687b9a15 100644 --- a/core/res/src/main/res/values-ja/strings.xml +++ b/core/res/src/main/res/values-ja/strings.xml @@ -1,5 +1,11 @@ + ポートフォリオにトークンが見つかりませんか?マーケットから見つけ、買付のために追加してください + ポートフォリオにトークンが見つかりませんか?マーケットから見つけ、売却のために追加してください + トークンを選択 + ポートフォリオにトークンが見つかりませんか?マーケットから見つけ、スワップのために追加してください + 選択したトークンと交換できるトークンがありません。別のトークンを選択してください。 + 利用可能なペアがありません ネットワークを選択 カスタムトークンの追加 トークンの管理 @@ -543,10 +549,21 @@ アクセスコードの復元 同一のカード アクセスコード + 国で検索 + 利用不可 + 通貨で検索 + 買付金額は%s以下にしてください + 買付金額は少なくとも%sである必要があります + この通貨で利用可能なプロバイダーはありません 支払う + サードパーティプロバイダー%sで取引を完了できます。 + %sにリダイレクトしています... この国では当社のサービスはご利用いただけません 変更または確認 あなたの住居は次のように識別されています + 住居 + 正確なお支払い方法とサービスを確保するため、正しい国を選択してください。 + 設定 経由 グループ 残高順 @@ -731,6 +748,8 @@ ステーキングにより%1$sを獲得できます。ステーキング報酬は毎週受け取れます。 安全にステーキングして、報酬を毎週獲得しましょう ステーキング報酬を獲得 + 残りのステーキング残高が少なすぎてステーキングを解除できません。最小のステーキング解除残高を満たすには、さらにステーキングする必要があります。 + ステーキング残高が低いです ネットワークの状態により、現在ステーキングはご利用いただけません。しばらくしてからもう一度お試しください。 新しいバリデーターで%1$sネットワークにステーキングすると、以前にステーキングされた資金はすべてこのバリデーターに自動的に転送されます。 獲得した報酬をステーキングに再投資し、潜在的な収益を増やします。 @@ -805,7 +824,6 @@ 金額には以下が含まれます: \n • サービス プロバイダーの手数料\n • 取引所からユーザーのアドレスに%1$sを送金するためのネットワーク手数料。 \n\nプロバイダーのスリッページは最大%2$sです この金額には、サービスプロバイダーの手数料が含まれています。 金額にはサービスプロバイダーの手数料が含まれます。 \n\nプロバイダーのスリッページは最大%s です - プロバイダーのスリッページは最大%sです。 手数料 すべての分散型取引所は、スマートコントラクトがあなたの許可なくウォレットにアクセスするのを防ぐために承認を必要とします。設計上、スマートコントラクトは承認なしでトークンにアクセスできません。トークンを「ロック解除」することで、あなたは1-inchのスマートコントラクトがトークンを使うことを承認します。ネットワークのマイナーは、このアクションをブロックチェーンに記録するためのガス料金(あなたが支払う)を受け取ります。承認後、トークンを交換することができます。 承認 @@ -844,6 +862,9 @@ このトークンは、2月%2$s-%3$s の間、%1$s のサービス手数料で別のトークンと交換できます。 Changellyでスワップ、手数料%s 今すぐスワップ + 買付できません + 売却できません + %sからのスワップは利用できません コントラクト: %s まだ取引はありません 取引履歴の読み込みに失敗しました。\n情報を更新するには、リロードボタンをクリックしてください。 @@ -853,6 +874,8 @@ 送金元: %s 送金先: %s バリデーター: %s + 最小%s + 最小取引金額は%1$sです。 もう一度やり直してください 同じカードをスキャンしました。ツインウォレットを作成するには、番号%dのカードをスキャンする必要があります。 間違ったツインカードをスキャンしました。別のカードをお試しください。 diff --git a/core/res/src/main/res/values-ru/strings.xml b/core/res/src/main/res/values-ru/strings.xml index e73b23b485..babcd2542e 100644 --- a/core/res/src/main/res/values-ru/strings.xml +++ b/core/res/src/main/res/values-ru/strings.xml @@ -1,5 +1,8 @@ + Выберите токен + Нет доступных токенов для обмена с выбранным токеном, пожалуйста, выберите другой. + Нет доступных пар Выберите сеть Добавить токен Валюты @@ -566,7 +569,14 @@ Восстановление кода доступа Идентичные карты Код доступа + Поиск по стране + Недоступно + Поиск по валюте + Сумма покупки не может быть больше, чем %s + Сумма покупки должна составлять минимум %s Оплата с + Наши сервисы недоступны в данной стране + Настройки Через Группы По балансу @@ -830,7 +840,6 @@ В сумму включено: \n• комиссия провайдера сервиса\n• комиссия сети за отправку %1$s от биржи обратно на адрес пользователя \n\nПроскальзывание провайдера составляет до %2$s В сумму включена комиссия провайдера сервиса. В сумму включена комиссия провайдера сервиса. \n\nПроскальзывание провайдера составляет до %s - Проскальзывание провайдера составляет до %s. Информация Подтверждения считаются отраслевым стандартом для всех децентрализованных бирж и защищают ваш кошелек от доступа со стороны смарт-контракта без вашего разрешения. По замыслу смарт-контракты не могут получить доступ к вашим токенам, если вы не одобрите доступ со своей стороны. «Разблокируя» свои токены, вы даете смарт-контракту 1inch разрешение тратить ваши активы. Майнеры сети получают компенсацию за газ (оплачиваемый вами) за запись этого действия в блокчейне. Как только разрешение будет предоставлено, вы сможете обменять свой токен. Подтвердить @@ -869,6 +878,8 @@ Обменивайте этот токен на другие с %1$s комиссии за обслуживание с %2$s по %3$s февраля. Обмен с Changelly, %s комиссии Обменять + Недоступно для покупки + Недоступно для продажи контракт: %s У вас еще нет транзакций Не удалось загрузить историю транзакций.\nНажмите на кнопку перезагрузки, чтобы обновить информацию. @@ -1016,7 +1027,7 @@ Сеть Солана испытывает высокую нагрузку. Если Ваша транзакция не прошла в течение 2 минут, повторите её отправку. Оповещение сети Солана Сеть Solana взимает арендную плату в размере %1$s каждые 2 дня. Аккаунты, которые не могут позволить себе арендную плату, удаляются из сети. Пополните свой счет более чем на %2$s, чтобы не платить арендную плату. - Некоторые сети в настоящее время недоступны. Пожалуйста, повторите попытку позже. + Свайпните вниз для обновления или попробуйте позже. Некоторые сети недоступны Это Testnet карта. Он не может обрабатывать транзакции и используется только в целях тестирования и разработки. Только для целей тестирования diff --git a/core/res/src/main/res/values-uk-rUA/strings.xml b/core/res/src/main/res/values-uk-rUA/strings.xml index 2697aa11d3..53de8082e3 100644 --- a/core/res/src/main/res/values-uk-rUA/strings.xml +++ b/core/res/src/main/res/values-uk-rUA/strings.xml @@ -1,5 +1,11 @@ + Токен не знайдено у вашому портфелі? Перевірте Ринки, щоб знайти та додати його для покупки + Токен не знайдено у вашому портфелі? Перевірте Ринки, щоб знайти та додати його для продажу + Оберіть токен + Токен не знайдено у вашому портфелі? Перевірте ринки, щоб знайти та додати його для обміну + Немає доступних токенів для обміну з обраним токеном. Будь ласка, оберіть інший. + Немає вільної пари Оберіть мережу Додати токен Токени @@ -520,7 +526,7 @@ Ваша seed-фраза - слово + %d слова %d слів %d слів @@ -567,9 +573,16 @@ Відновлення коду доступу Ідентичні картки Код доступу + Пошук за країною + Недоступно + Пошук по валюті + Оплата з Наші сервіси недоступні в цій країні Змінити або підтвердити Ваше місце проживання визначено як + Будь ласка, виберіть правильну країну, щоб забезпечити точні способи оплати та послуги. + Параметри + Через Групами За балансом Сортування токенів @@ -716,6 +729,7 @@ Ім\'я Сума для стейкінгу має бути не менше %s Сума стейкінгу буде округлена до %1$s TRX відповідно до правил мережі. + Сума зняття зі стейкінгу буде округлена до %1$s TRX через мережеві правила. Зняти кошти Комісія за стейкінг-акаунт Стейкінг-акаунт - це спеціальний рахунок, на якому зберігаються застейкані SOL токени. Він створюється, коли ви делегуєте свої токени валідатору для участі у перевірці транзакцій та отримання винагород. За створення стейкінг-акаунту стягується невелика комісія, яка повертається після завершення стейкінгу. @@ -758,10 +772,13 @@ Стейкінг дає змогу вам отримувати %1$s. Винагорода буде зараховуватися кожен тиждень. Стейкайте безпечно та почніть отримувати винагороди щотижня Отримуйте винагороду за стейкінг + Ваш баланс стейкінгу, що залишився, буде занадто низьким, щоб вивести його зі стейкінгу. Вам потрібно буде застейкати більше, щоб досягти мінімальної суми для виводу. + Низький баланс стейкінгу Стейкінг тимчасово недоступний через проблеми в мережі. Будь ласка, спробуйте пізніше. Стейкінг в мережі %1$s з новим валідатором автоматично переведе всі раніше застейкані кошти до цього валідатора Реінвестуйте зароблені винагороди у суму стейкінгу, щоб збільшити потенційний прибуток. Рестейк дозволяє вам перемістити ваші кошти від одного валідатора до іншого без необхідності виводити кошти зі стейкінгу + Ви збираєтеся застейкати весь свій баланс. Ми рекомендуємо залишити невелику суму, щоб покрити комісію мережі за зняття коштів або отримання винагороди. Розблокуйте свої кошти, щоб вивести їх зі стейкінгу. Розблокування займе %s. Ваші кошти будуть доступні для використання після закінчення 21-денного періоду розблокування. Винагорода буде отримана разом з вашими незастейканими коштами. Ваші кошти будуть доступні після %s періоду розблокування. @@ -790,6 +807,7 @@ Винагороди Стейкінг закрито Застейкати більше + Застейканий залишок Ви стейкаєте %1$s і будете отримуватиме винагороду %2$s Натисніть, щоб розблокувати Натисніть, щоб розблокувати або проголосувати @@ -868,6 +886,9 @@ Обмінюйте цей токен на інші з %1$s комісією за обслуговування з %2$s по %3$s лютого. Обмін із Changelly, %s комісії Обміняти + Недоступно для покупки + Недоступно для продажу + Недоступно для обміну з %s контракт: %s У вас ще немає транзакцій Не вдалося завантажити історію транзакцій.\nНатисніть кнопку нижче, щоб оновити інформацію @@ -877,6 +898,7 @@ від: %s до: %s валідатор: %s + Мінімальна сума транзакції становить %1$s. Спробуйте знову Ви відсканували одну й ту саму картку. Для створення twin-гаманця вам потрібно відсканувати картку з номером %d Ви відсканували не ту twin-картку. Будь ласка, спробуйте відсканувати іншу @@ -1014,7 +1036,7 @@ Мережа Солана зазнає високого навантаження. Якщо транзакція не пройшла протягом 2 хвилин, повторіть транзакцію. Оповіщення мережі Солана Мережа Solana стягує орендну плату у розмірі %1$s кожні 2 дні. Акаунти, які не можуть дозволити собі орендну плату, видаляються з мережі. Поповніть свій рахунок на суму понад %2$s, щоб не платити орендну плату. - Деякі мережі наразі недоступні. Будь ласка, спробуйте пізніше. + Проведіть пальцем вниз для оновлення або спробуйте пізніше. Деякі мережі недоступні Це картка Testnet. Вона не може обробляти транзакції і повинна використовуватися лише для тестування та розробки. Лише для цілей тестування diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml index c2f7e4c756..ef0cd2f4f5 100644 --- a/core/res/src/main/res/values/strings.xml +++ b/core/res/src/main/res/values/strings.xml @@ -1,5 +1,11 @@ + Token not found in your portfolio? Check the Markets to find and add it for purchase + Token not found in your portfolio? Check the Markets to find and add it for sell + Choose the Token + Token not found in your portfolio? Check the Markets to find and add it for swap + There are no available tokens to swap with the selected token. Please choose another one. + No available pair Choose network Add custom token Manage tokens @@ -554,7 +560,12 @@ Search by country Unavailable Search by currency + The purchase amount should be no more than %s + The amount to buy must be at least %s + No available providers for this currency Pay with + You will be able to complete your transaction on the third-party provider, %s + Redirecting to %s... Our services are not available in this country Change or confirm it Your residence has been identified as @@ -823,7 +834,6 @@ The amount includes:\n• service provider\'s fee\n• network fee for sending %1$s from the exchange back to the user\'s address. \n\nProvider slippage is up to %2$s The amount includes the service provider\'s fee. The amount includes the service provider\'s fee. \n\nProvider slippage is up to %s - Provider slippage is up to %s. Information All decentralized exchanges require approvals to prevent smart contracts from accessing your wallet without your permission. By design, smart contracts can\'t access your tokens unless you approve. By \"unlocking\" your tokens, you authorize the 1-inch smart contract to spend them. The network\'s miners receive a gas fee (paid by you) to record this action on the blockchain. You can swap your token after giving approval. Approve @@ -862,6 +872,9 @@ Exchange this token for another at %1$s service fees from February %2$s-%3$s. Swap with Changelly, %s fees Swap now + Unavailable to purchase + Unavailable to sell + Unavailable for swap from %s contract: %s You don\'t have any transactions yet Failed to load transaction history.\nClick on reload button to update the information. @@ -871,6 +884,7 @@ from: %s to: %s validator: %s + Minimum %s The minimum transaction amount is %1$s. Try again You\'ve scanned the same card. To create a twin wallet you need to scan the card with number %d @@ -1007,7 +1021,7 @@ The Solana network is congested. If your transaction is not processed within 2 minutes, please repeat the transaction. Solana Network Alert Solana network charges a rent of %1$s every 2 days. Accounts that can\'t afford the rent are purged from the network. Deposit your account with more than %2$s to use it for free. - Some networks currently are unreachable. Please try again later. + Swipe down to refresh or try again later. Some networks are unreachable This is a Testnet card. It cannot process transactions and should only be used for testing and development purposes. For testing purposes only From 1df2f17b7946d7062643a92ca899d43ad8fccb4a Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 11 Nov 2024 11:40:19 +0200 Subject: [PATCH 100/106] Updated on 2026-08-14 --- .../InvalidatePendingTransactionsUseCase.kt | 45 ++++++++++++++++++- 1 file changed, 43 insertions(+), 2 deletions(-) diff --git a/domain/staking/src/main/java/com/tangem/domain/staking/InvalidatePendingTransactionsUseCase.kt b/domain/staking/src/main/java/com/tangem/domain/staking/InvalidatePendingTransactionsUseCase.kt index eb07d96ab9..29e7148ee2 100644 --- a/domain/staking/src/main/java/com/tangem/domain/staking/InvalidatePendingTransactionsUseCase.kt +++ b/domain/staking/src/main/java/com/tangem/domain/staking/InvalidatePendingTransactionsUseCase.kt @@ -8,6 +8,7 @@ import com.tangem.domain.staking.model.stakekit.action.StakingAction import com.tangem.domain.staking.model.stakekit.action.StakingActionType import com.tangem.domain.staking.repositories.StakingErrorResolver import com.tangem.utils.extensions.isEqualTo +import java.math.BigDecimal import java.util.UUID class InvalidatePendingTransactionsUseCase( @@ -50,9 +51,15 @@ class InvalidatePendingTransactionsUseCase( StakingActionType.UNLOCK_LOCKED -> { modifyBalancesByStatus(balances, action, BalanceType.LOCKED) } - StakingActionType.UNSTAKE, StakingActionType.RESTAKE -> { + StakingActionType.RESTAKE -> { modifyBalancesByStatus(balances, action, BalanceType.STAKED) } + StakingActionType.UNSTAKE -> { + val isFullUnstake = modifyBalancesByStatus(balances, action, BalanceType.STAKED) + if (!isFullUnstake) { + processPartialUnstake(balances, action) + } + } else -> { // intentionally do nothing } @@ -86,12 +93,19 @@ class InvalidatePendingTransactionsUseCase( ) } - private fun modifyBalancesByStatus(balances: MutableList, action: StakingAction, type: BalanceType) { + private fun modifyBalancesByStatus( + balances: MutableList, + action: StakingAction, + type: BalanceType, + ): Boolean { val index = findBalanceIndex(balances, action, type) if (index != -1) { balances[index] = balances[index].copy(isPending = true) + return true } + + return false } private fun findBalanceIndex(balances: MutableList, action: StakingAction, type: BalanceType): Int { @@ -99,4 +113,31 @@ class InvalidatePendingTransactionsUseCase( !it.isPending && it.amount isEqualTo action.amount && it.type == type } } + + private fun processPartialUnstake(balances: MutableList, action: StakingAction) { + val (index, pendingActionAmount) = findPartialUnstake(balances, action) + + if (index != -1) { + val amount = balances[index].amount + balances[index] = balances[index].copy( + amount = amount - pendingActionAmount, + ) // remnants of real one + + balances.add( + balances[index].copy( + amount = pendingActionAmount, + isPending = true, + ), + ) // pending with amount from action + } + } + + private fun findPartialUnstake(balances: MutableList, action: StakingAction): Pair { + val index = balances.indexOfFirst { + !it.isPending && action.amount < it.amount && + it.type == BalanceType.STAKED && + it.validatorAddress == action.validatorAddress + } + return index to action.amount + } } \ No newline at end of file From 1fea0c7c4848406fe28567700579b91dcb84c74b Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 11 Nov 2024 11:40:29 +0200 Subject: [PATCH 101/106] Updated on 2026-08-14 --- .../api/stakekit/models/response/model/YieldDTO.kt | 8 ++++---- .../com/tangem/data/staking/DefaultStakingRepository.kt | 2 +- .../com/tangem/data/staking/converters/YieldConverter.kt | 8 ++++---- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/stakekit/models/response/model/YieldDTO.kt b/core/datasource/src/main/java/com/tangem/datasource/api/stakekit/models/response/model/YieldDTO.kt index 951d60ddf7..88cc922696 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/stakekit/models/response/model/YieldDTO.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/stakekit/models/response/model/YieldDTO.kt @@ -7,7 +7,7 @@ import java.math.BigDecimal @JsonClass(generateAdapter = true) data class YieldDTO( @Json(name = "id") - val id: String, + val id: String?, @Json(name = "token") val token: TokenDTO?, @Json(name = "tokens") @@ -67,9 +67,9 @@ data class YieldDTO( @Json(name = "address") val address: String?, @Json(name = "status") - val status: ValidatorStatusDTO, + val status: ValidatorStatusDTO?, @Json(name = "name") - val name: String, + val name: String?, @Json(name = "image") val image: String?, @Json(name = "website") @@ -83,7 +83,7 @@ data class YieldDTO( @Json(name = "votingPower") val votingPower: Double?, @Json(name = "preferred") - val preferred: Boolean, + val preferred: Boolean?, ) { @JsonClass(generateAdapter = true) enum class ValidatorStatusDTO { diff --git a/data/staking/src/main/java/com/tangem/data/staking/DefaultStakingRepository.kt b/data/staking/src/main/java/com/tangem/data/staking/DefaultStakingRepository.kt index 1a40afb7ac..3261381510 100644 --- a/data/staking/src/main/java/com/tangem/data/staking/DefaultStakingRepository.kt +++ b/data/staking/src/main/java/com/tangem/data/staking/DefaultStakingRepository.kt @@ -589,7 +589,7 @@ internal class DefaultStakingRepository( private fun getEnabledYields(): List { return yieldConverter.convertListIgnoreErrors( input = stakingYieldsStore.get(), - onError = { Timber.e("Error converting enabled yields list: $it") }, + onError = { Timber.e("Error converting one of the items in enabled yields: $it") }, ) } diff --git a/data/staking/src/main/java/com/tangem/data/staking/converters/YieldConverter.kt b/data/staking/src/main/java/com/tangem/data/staking/converters/YieldConverter.kt index 52993093c5..6d6b14b10b 100644 --- a/data/staking/src/main/java/com/tangem/data/staking/converters/YieldConverter.kt +++ b/data/staking/src/main/java/com/tangem/data/staking/converters/YieldConverter.kt @@ -118,16 +118,16 @@ class YieldConverter( val address = validatorDTO.address.asMandatory("address") return Yield.Validator( address = address, - status = convertValidatorStatus(validatorDTO.status), - name = validatorDTO.name, + status = convertValidatorStatus(validatorDTO.status.asMandatory("status")), + name = validatorDTO.name.asMandatory("name"), image = validatorDTO.image, website = validatorDTO.website, apr = validatorDTO.apr, commission = validatorDTO.commission, stakedBalance = validatorDTO.stakedBalance, votingPower = validatorDTO.votingPower, - preferred = validatorDTO.preferred, - isStrategicPartner = isStrategicPartner(validatorDTO.address, validatorDTO.name), + preferred = validatorDTO.preferred.asMandatory("preferred"), + isStrategicPartner = isStrategicPartner(validatorDTO.address, validatorDTO.name.asMandatory("name")), ) } From 3a1101ceadc2f48637ed27e20d6047215558da36 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 11 Nov 2024 11:40:39 +0200 Subject: [PATCH 102/106] Updated on 2026-08-14 --- .../impl/presentation/ui/StakingInitialInfoContent.kt | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingInitialInfoContent.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingInitialInfoContent.kt index 1fbbff9df2..95038b16d9 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingInitialInfoContent.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingInitialInfoContent.kt @@ -165,9 +165,13 @@ private fun LazyListScope.activeStakingBlock( } itemsIndexed( items = state.yieldBalance.balances, - key = { _, balance -> + key = { index, balance -> // Staked balance does not have unique identifier. - balance.toString() + buildString { + append(balance.hashCode()) + append("_") + append(index) + } }, ) { index, balance -> ActiveStakingBlock( From 25c73300fef1f865f91ef8663d8c06d8c95c122e Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 11 Nov 2024 13:05:59 +0300 Subject: [PATCH 103/106] Updated on 2026-08-14 --- .../data/DefaultLegacyWalletConnectRepository.kt | 6 ++++-- gradle/dependencies.toml | 4 ++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect2/data/DefaultLegacyWalletConnectRepository.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect2/data/DefaultLegacyWalletConnectRepository.kt index 8f80642201..9e980da2ba 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletconnect2/data/DefaultLegacyWalletConnectRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletconnect2/data/DefaultLegacyWalletConnectRepository.kt @@ -207,6 +207,10 @@ internal class DefaultLegacyWalletConnectRepository( Timber.i("onSessionDelete: $sessionDelete") } + override fun onSessionExtend(session: Wallet.Model.Session) { + Timber.i("onSessionExtend: $session") + } + override fun onSessionSettleResponse(settleSessionResponse: Wallet.Model.SettledSessionResponse) { // Triggered when wallet receives the session settlement response from Dapp Timber.i("onSessionSettleResponse: $settleSessionResponse") @@ -264,8 +268,6 @@ internal class DefaultLegacyWalletConnectRepository( } override fun approve(userNamespaces: Map>) { - this.userNamespaces = userNamespaces - val sessionProposal: Wallet.Model.SessionProposal = requireNotNull(this.sessionProposal) val userChains = userNamespaces.flatMap { namespace -> diff --git a/gradle/dependencies.toml b/gradle/dependencies.toml index 2e813b0545..ba3c3819e0 100644 --- a/gradle/dependencies.toml +++ b/gradle/dependencies.toml @@ -69,8 +69,8 @@ zxingQrCode = "3.5.1" mviCore = "1.3.1" kotlinSerialization = "1.4.1" arrow = "1.2.3" -walletConnectCore = "1.18.0" -walletConnectWeb3 = "1.11.0" +walletConnectCore = "1.35.2" +walletConnectWeb3 = "1.35.2" prettyLogger = "2.2.0" okHttp-prettyLogging = "3.1.0" chucker = "4.0.0" From 0ddd171bb273117aca71c84d681891b5ad893d87 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 11 Nov 2024 12:42:36 +0300 Subject: [PATCH 104/106] Updated on 2026-08-14 --- .../tap/di/domain/TokensDomainModule.kt | 2 - .../bigdecimal/BigDecimalCryptoFormat.kt | 37 ++++-- .../tangem/data/tokens/di/TokensDataModule.kt | 10 +- .../DefaultCurrencyChecksRepository.kt | 15 ++- .../GetMinimumTransactionAmountSyncUseCase.kt | 11 +- .../tangem/feature/swap/ui/StateBuilder.kt | 55 +++++++-- .../feature/swap/viewmodels/SwapViewModel.kt | 110 +++++++++++------- 7 files changed, 155 insertions(+), 85 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/di/domain/TokensDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/TokensDomainModule.kt index 0776b43400..7e547182ce 100644 --- a/app/src/main/java/com/tangem/tap/di/domain/TokensDomainModule.kt +++ b/app/src/main/java/com/tangem/tap/di/domain/TokensDomainModule.kt @@ -277,11 +277,9 @@ internal object TokensDomainModule { @Singleton fun provideGetMinimumTransactionAmountSyncUseCase( currencyChecksRepository: CurrencyChecksRepository, - dispatchers: CoroutineDispatcherProvider, ): GetMinimumTransactionAmountSyncUseCase { return GetMinimumTransactionAmountSyncUseCase( currencyChecksRepository = currencyChecksRepository, - dispatchers = dispatchers, ) } diff --git a/core/ui/src/main/java/com/tangem/core/ui/format/bigdecimal/BigDecimalCryptoFormat.kt b/core/ui/src/main/java/com/tangem/core/ui/format/bigdecimal/BigDecimalCryptoFormat.kt index eea3130209..7d6849591c 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/format/bigdecimal/BigDecimalCryptoFormat.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/format/bigdecimal/BigDecimalCryptoFormat.kt @@ -5,6 +5,7 @@ import com.tangem.core.ui.format.bigdecimal.BigDecimalFormatConstants.CRYPTO_FEE import com.tangem.core.ui.format.bigdecimal.BigDecimalFormatConstants.CURRENCY_SPACE import com.tangem.core.ui.format.bigdecimal.BigDecimalFormatConstants.FORMAT_THRESHOLD import com.tangem.domain.tokens.model.CryptoCurrency +import com.tangem.utils.StringsSigns.NON_BREAKING_SPACE import com.tangem.utils.extensions.isNotWhitespace import java.math.BigDecimal import java.math.RoundingMode @@ -16,6 +17,7 @@ open class BigDecimalCryptoFormat( val symbol: String, val decimals: Int, val locale: Locale = Locale.getDefault(), + val ignoreSymbolPosition: Boolean = false, ) : BigDecimalFormat { override fun invoke(value: BigDecimal): String = defaultAmount()(value) @@ -48,11 +50,13 @@ fun BigDecimalFormatScope.crypto( fun BigDecimalFormatScope.crypto( cryptoCurrency: CryptoCurrency, + ignoreSymbolPosition: Boolean = false, locale: Locale = Locale.getDefault(), ): BigDecimalCryptoFormat { return BigDecimalCryptoFormat( symbol = cryptoCurrency.symbol, decimals = cryptoCurrency.decimals, + ignoreSymbolPosition = ignoreSymbolPosition, locale = locale, ) } @@ -60,19 +64,28 @@ fun BigDecimalFormatScope.crypto( // == Formatters == fun BigDecimalCryptoFormat.defaultAmount() = BigDecimalFormat { value -> - val formatter = NumberFormat.getCurrencyInstance(locale).apply { - currency = usdCurrency - maximumFractionDigits = decimals.coerceAtMost(maximumValue = 8) - minimumFractionDigits = 2 - isGroupingUsed = true - roundingMode = RoundingMode.HALF_UP + if (ignoreSymbolPosition) { + val formatter = NumberFormat.getInstance(locale).apply { + maximumFractionDigits = decimals.coerceAtMost(maximumValue = 8) + minimumFractionDigits = 2 + isGroupingUsed = true + roundingMode = RoundingMode.HALF_UP + } + formatter.format(value) + NON_BREAKING_SPACE + symbol + } else { + val formatter = NumberFormat.getCurrencyInstance(locale).apply { + currency = usdCurrency + maximumFractionDigits = decimals.coerceAtMost(maximumValue = 8) + minimumFractionDigits = 2 + isGroupingUsed = true + roundingMode = RoundingMode.HALF_UP + } + formatter.format(value) + .replaceFiatSymbolWithCrypto( + fiatCurrencySymbol = usdCurrency.getSymbol(locale), + cryptoCurrencySymbol = symbol, + ) } - - formatter.format(value) - .replaceFiatSymbolWithCrypto( - fiatCurrencySymbol = usdCurrency.getSymbol(locale), - cryptoCurrencySymbol = symbol, - ) } fun BigDecimalCryptoFormat.shorted() = BigDecimalFormat { value -> diff --git a/data/tokens/src/main/kotlin/com/tangem/data/tokens/di/TokensDataModule.kt b/data/tokens/src/main/kotlin/com/tangem/data/tokens/di/TokensDataModule.kt index 3e5c70b106..cc7bb7a3fe 100644 --- a/data/tokens/src/main/kotlin/com/tangem/data/tokens/di/TokensDataModule.kt +++ b/data/tokens/src/main/kotlin/com/tangem/data/tokens/di/TokensDataModule.kt @@ -95,8 +95,14 @@ internal object TokensDataModule { @Provides @Singleton - fun provideCurrencyChecksRepository(walletManagersFacade: WalletManagersFacade): CurrencyChecksRepository { - return DefaultCurrencyChecksRepository(walletManagersFacade = walletManagersFacade) + fun provideCurrencyChecksRepository( + walletManagersFacade: WalletManagersFacade, + coroutineDispatcherProvider: CoroutineDispatcherProvider, + ): CurrencyChecksRepository { + return DefaultCurrencyChecksRepository( + walletManagersFacade = walletManagersFacade, + coroutineDispatchers = coroutineDispatcherProvider, + ) } @Provides diff --git a/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultCurrencyChecksRepository.kt b/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultCurrencyChecksRepository.kt index b156d85caf..a62d70a3f8 100644 --- a/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultCurrencyChecksRepository.kt +++ b/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultCurrencyChecksRepository.kt @@ -12,10 +12,13 @@ import com.tangem.domain.tokens.model.blockchains.UtxoAmountLimit import com.tangem.domain.tokens.repository.CurrencyChecksRepository import com.tangem.domain.walletmanager.WalletManagersFacade import com.tangem.domain.wallets.models.UserWalletId +import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import kotlinx.coroutines.withContext import java.math.BigDecimal internal class DefaultCurrencyChecksRepository( private val walletManagersFacade: WalletManagersFacade, + private val coroutineDispatchers: CoroutineDispatcherProvider, ) : CurrencyChecksRepository { override suspend fun getExistentialDeposit(userWalletId: UserWalletId, network: Network): BigDecimal? { val manager = walletManagersFacade.getOrCreateWalletManager( @@ -45,12 +48,14 @@ internal class DefaultCurrencyChecksRepository( } override suspend fun getMinimumSendAmount(userWalletId: UserWalletId, network: Network): BigDecimal? { - val manager = walletManagersFacade.getOrCreateWalletManager( - userWalletId = userWalletId, - network = network, - ) + return withContext(coroutineDispatchers.io) { + val manager = walletManagersFacade.getOrCreateWalletManager( + userWalletId = userWalletId, + network = network, + ) - return if (manager is MinimumSendAmountProvider) manager.getMinimumSendAmount() else null + if (manager is MinimumSendAmountProvider) manager.getMinimumSendAmount() else null + } } override suspend fun getFeeResourceAmount(userWalletId: UserWalletId, network: Network): CurrencyAmount? { diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetMinimumTransactionAmountSyncUseCase.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetMinimumTransactionAmountSyncUseCase.kt index 73aaeb8c0d..388b88d7e7 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetMinimumTransactionAmountSyncUseCase.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetMinimumTransactionAmountSyncUseCase.kt @@ -5,22 +5,17 @@ import arrow.core.raise.either import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.domain.tokens.repository.CurrencyChecksRepository import com.tangem.domain.wallets.models.UserWalletId -import com.tangem.utils.coroutines.CoroutineDispatcherProvider -import kotlinx.coroutines.withContext import java.math.BigDecimal class GetMinimumTransactionAmountSyncUseCase( private val currencyChecksRepository: CurrencyChecksRepository, - private val dispatchers: CoroutineDispatcherProvider, ) { suspend operator fun invoke( userWalletId: UserWalletId, cryptoCurrencyStatus: CryptoCurrencyStatus, - ): Either = withContext(dispatchers.io) { - either { - val cryptoCurrency = cryptoCurrencyStatus.currency - currencyChecksRepository.getMinimumSendAmount(userWalletId, cryptoCurrency.network) - } + ): Either = either { + val cryptoCurrency = cryptoCurrencyStatus.currency + currencyChecksRepository.getMinimumSendAmount(userWalletId, cryptoCurrency.network) } } \ No newline at end of file diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt index d5f2d4d92b..e841fb0bba 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt @@ -172,10 +172,15 @@ internal class StateBuilder( val canSelectReceiveToken = mainTokenId != toToken.id.value if (uiStateHolder.sendCardData !is SwapCardState.SwapCardData) return uiStateHolder if (uiStateHolder.receiveCardData !is SwapCardState.SwapCardData) return uiStateHolder - val sendInput = requireNotNull(uiStateHolder.sendCardData.type as? TransactionCardType.Inputtable).copy( - isError = false, - header = TextReference.Res(R.string.swapping_from_title), - ) + val sendInputType = requireNotNull(uiStateHolder.sendCardData.type as? TransactionCardType.Inputtable) + val sendInput = if (sendInputType.isError) { + sendInputType + } else { + sendInputType.copy( + isError = false, + header = TextReference.Res(R.string.swapping_from_title), + ) + } return uiStateHolder.copy( sendCardData = SwapCardState.SwapCardData( type = sendInput, @@ -252,10 +257,16 @@ internal class StateBuilder( } else { TextReference.Res(R.string.swapping_from_title) } - val sendInput = requireNotNull(uiStateHolder.sendCardData.type as? TransactionCardType.Inputtable).copy( - isError = isInsufficientFunds, - header = insufficientFundsHeader, - ) + val sendCardType = requireNotNull(uiStateHolder.sendCardData.type as? TransactionCardType.Inputtable) + val sendInput = if (sendCardType.isError && !isInsufficientFunds) { + // if any error in inputField and funds enough -> show that error else show fund is not enough error + sendCardType + } else { + sendCardType.copy( + isError = isInsufficientFunds, + header = insufficientFundsHeader, + ) + } return uiStateHolder.copy( sendCardData = SwapCardState.SwapCardData( type = sendInput, @@ -863,14 +874,36 @@ internal class StateBuilder( ) } - fun updateSwapAmount(uiState: SwapStateHolder, amount: String): SwapStateHolder { + fun updateSwapAmount( + uiState: SwapStateHolder, + amountFormatted: String, + amountRaw: String, + fromToken: CryptoCurrency, + minTxAmount: BigDecimal?, + ): SwapStateHolder { if (uiState.sendCardData !is SwapCardState.SwapCardData) return uiState + val amountToSend = amountRaw.toBigDecimalOrNull() + val sendInput = if (minTxAmount != null && amountToSend != null && amountToSend < minTxAmount) { + val minAmountFormatted = minTxAmount.format { + crypto(cryptoCurrency = fromToken, ignoreSymbolPosition = true) + } + (uiState.sendCardData.type as? TransactionCardType.Inputtable)?.copy( + isError = true, + header = resourceReference(R.string.transfer_min_amount_error, wrappedList(minAmountFormatted)), + ) ?: uiState.sendCardData.type + } else { + (uiState.sendCardData.type as? TransactionCardType.Inputtable)?.copy( + isError = false, + header = TextReference.Res(R.string.swapping_from_title), + ) ?: uiState.sendCardData.type + } return uiState.copy( sendCardData = uiState.sendCardData.copy( amountTextFieldValue = TextFieldValue( - text = amount, - selection = TextRange(amount.length), + text = amountFormatted, + selection = TextRange(amountFormatted.length), ), + type = sendInput, ), ) } diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/viewmodels/SwapViewModel.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/viewmodels/SwapViewModel.kt index c4854ddb29..216f2860fa 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/viewmodels/SwapViewModel.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/viewmodels/SwapViewModel.kt @@ -26,6 +26,7 @@ import com.tangem.domain.feedback.models.BlockchainErrorInfo import com.tangem.domain.feedback.models.FeedbackEmailType import com.tangem.domain.tokens.GetCryptoCurrencyStatusSyncUseCase import com.tangem.domain.tokens.GetCurrencyStatusUpdatesUseCase +import com.tangem.domain.tokens.GetMinimumTransactionAmountSyncUseCase import com.tangem.domain.tokens.UpdateDelayedNetworkStatusUseCase import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.tokens.model.CryptoCurrencyStatus @@ -83,6 +84,7 @@ internal class SwapViewModel @Inject constructor( private val getCardInfoUseCase: GetCardInfoUseCase, private val saveBlockchainErrorUseCase: SaveBlockchainErrorUseCase, private val sendFeedbackEmailUseCase: SendFeedbackEmailUseCase, + private val getMinimumTransactionAmountSyncUseCase: GetMinimumTransactionAmountSyncUseCase, swapInteractorFactory: SwapInteractor.Factory, savedStateHandle: SavedStateHandle, ) : ViewModel(), DefaultLifecycleObserver { @@ -842,60 +844,78 @@ internal class SwapViewModel @Inject constructor( } private fun onChangeCardsClicked() { - val newFromToken = dataState.toCryptoCurrency - val newToToken = dataState.fromCryptoCurrency + viewModelScope.launch { + val newFromToken = dataState.toCryptoCurrency + val newToToken = dataState.fromCryptoCurrency - if (newFromToken != null && newToToken != null) { - isAmountChangedByUser = true + if (newFromToken != null && newToToken != null) { + isAmountChangedByUser = true - dataState = dataState.copy( - fromCryptoCurrency = newFromToken, - toCryptoCurrency = newToToken, - ) - isOrderReversed = !isOrderReversed - dataState.tokensDataState?.let { - updateTokensState(it) + dataState = dataState.copy( + fromCryptoCurrency = newFromToken, + toCryptoCurrency = newToToken, + ) + isOrderReversed = !isOrderReversed + dataState.tokensDataState?.let { + updateTokensState(it) + } + + val minTxAmount = getMinimumTransactionAmountSyncUseCase( + userWalletId, + newFromToken, + ).getOrNull() + val decimals = newFromToken.currency.decimals + lastAmount.value = cutAmountWithDecimals(decimals, lastAmount.value) + uiState = stateBuilder.updateSwapAmount( + uiState = uiState, + amountFormatted = inputNumberFormatter.formatWithThousands(lastAmount.value, decimals), + amountRaw = lastAmount.value, + fromToken = newFromToken.currency, + minTxAmount = minTxAmount, + ) + startLoadingQuotes( + fromToken = newFromToken, + toToken = newToToken, + amount = lastAmount.value, + toProvidersList = findSwapProviders(newFromToken, newToToken), + ) } - - val decimals = newFromToken.currency.decimals - lastAmount.value = cutAmountWithDecimals(decimals, lastAmount.value) - uiState = stateBuilder.updateSwapAmount( - uiState, - inputNumberFormatter.formatWithThousands(lastAmount.value, decimals), - ) - startLoadingQuotes( - fromToken = newFromToken, - toToken = newToToken, - amount = lastAmount.value, - toProvidersList = findSwapProviders(newFromToken, newToToken), - ) } } private fun onAmountChanged(value: String) { - val fromToken = dataState.fromCryptoCurrency - val toToken = dataState.toCryptoCurrency - if (fromToken != null) { - val decimals = fromToken.currency.decimals - val cutValue = cutAmountWithDecimals(decimals, value) - lastAmount.value = cutValue - uiState = stateBuilder.updateSwapAmount( - uiState = uiState, - amount = inputNumberFormatter.formatWithThousands(cutValue, decimals), - ) + viewModelScope.launch { + val fromToken = dataState.fromCryptoCurrency + val toToken = dataState.toCryptoCurrency + if (fromToken != null) { + val decimals = fromToken.currency.decimals + val cutValue = cutAmountWithDecimals(decimals, value) + val minTxAmount = getMinimumTransactionAmountSyncUseCase( + userWalletId, + fromToken, + ).getOrNull() + lastAmount.value = cutValue + uiState = stateBuilder.updateSwapAmount( + uiState = uiState, + amountFormatted = inputNumberFormatter.formatWithThousands(cutValue, decimals), + amountRaw = lastAmount.value, + fromToken = fromToken.currency, + minTxAmount = minTxAmount, + ) - if (toToken != null) { - if (toToken.value.amount != null) { - isAmountChangedByUser = true - } + if (toToken != null) { + if (toToken.value.amount != null) { + isAmountChangedByUser = true + } - amountDebouncer.debounce(viewModelScope, DEBOUNCE_AMOUNT_DELAY) { - startLoadingQuotes( - fromToken = fromToken, - toToken = toToken, - amount = lastAmount.value, - toProvidersList = findSwapProviders(fromToken, toToken), - ) + amountDebouncer.debounce(viewModelScope, DEBOUNCE_AMOUNT_DELAY) { + startLoadingQuotes( + fromToken = fromToken, + toToken = toToken, + amount = lastAmount.value, + toProvidersList = findSwapProviders(fromToken, toToken), + ) + } } } } From fa206b842261e81f8257636d6093ad3ebf71595a Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 11 Nov 2024 16:57:11 +0300 Subject: [PATCH 105/106] Updated on 2026-08-14 --- gradle/dependencies.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/dependencies.toml b/gradle/dependencies.toml index ba3c3819e0..cf8b89cfd5 100644 --- a/gradle/dependencies.toml +++ b/gradle/dependencies.toml @@ -90,7 +90,7 @@ markdownComposeView = "0.5.4" # region Tangem tangemBlockchainSdk = "release-app_5.18-856" #tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds -tangemCardSdk = "release-app_5.18-396" +tangemCardSdk = "release-app_5.18-398" #tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^ tangemVico = "2.0.0-alpha.25-tangem17" #tangemVico = "0.0.1" # Keep it! - used for local builds ^ From dba36d934f191322442cb2e5965ae4af32c51ede Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 12 Nov 2024 10:11:07 +0300 Subject: [PATCH 106/106] Updated on 2026-08-14 --- gradle/dependencies.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/dependencies.toml b/gradle/dependencies.toml index cf8b89cfd5..cb7a4dbffc 100644 --- a/gradle/dependencies.toml +++ b/gradle/dependencies.toml @@ -88,7 +88,7 @@ markdownComposeView = "0.5.4" # endregion Other libraries # region Tangem -tangemBlockchainSdk = "release-app_5.18-856" +tangemBlockchainSdk = "release-app_5.18-859" #tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds tangemCardSdk = "release-app_5.18-398" #tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^