diff --git a/app/src/main/assets/tangem-app-config b/app/src/main/assets/tangem-app-config index 098662beb5..8285f8d289 160000 --- a/app/src/main/assets/tangem-app-config +++ b/app/src/main/assets/tangem-app-config @@ -1 +1 @@ -Subproject commit 098662beb5b0123b11f5ee4873d4bd667ac93c73 +Subproject commit 8285f8d2896d2994337f55a7b3a54fd5acc24304 diff --git a/app/src/main/java/com/tangem/tap/di/domain/YieldSupplyDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/YieldSupplyDomainModule.kt index d74844c082..c893ae8a08 100644 --- a/app/src/main/java/com/tangem/tap/di/domain/YieldSupplyDomainModule.kt +++ b/app/src/main/java/com/tangem/tap/di/domain/YieldSupplyDomainModule.kt @@ -204,6 +204,16 @@ internal object YieldSupplyDomainModule { ) } + @Provides + @Singleton + fun provideYieldSupplyEnterStatusFlowUseCase( + yieldSupplyRepository: YieldSupplyRepository, + ): YieldSupplyEnterStatusFlowUseCase { + return YieldSupplyEnterStatusFlowUseCase( + yieldSupplyRepository = yieldSupplyRepository, + ) + } + @Provides @Singleton fun provideYieldSupplyGetShouldShowMainPromoUseCase( diff --git a/common/ui/src/main/java/com/tangem/common/ui/news/ArticleCard.kt b/common/ui/src/main/java/com/tangem/common/ui/news/ArticleCard.kt index e0086f9b0c..43f01a3e93 100644 --- a/common/ui/src/main/java/com/tangem/common/ui/news/ArticleCard.kt +++ b/common/ui/src/main/java/com/tangem/common/ui/news/ArticleCard.kt @@ -95,8 +95,6 @@ private fun TrendingArticle( TangemTheme.colors.text.primary1 }, style = TangemTheme.typography.h3, - maxLines = 3, - overflow = TextOverflow.Ellipsis, textAlign = TextAlign.Center, ) diff --git a/core/config-toggles/src/main/assets/configs/feature_toggles_config.json b/core/config-toggles/src/main/assets/configs/feature_toggles_config.json index 1c24b1d40c..5dc9541240 100644 --- a/core/config-toggles/src/main/assets/configs/feature_toggles_config.json +++ b/core/config-toggles/src/main/assets/configs/feature_toggles_config.json @@ -47,10 +47,6 @@ "name": "TANGEM_PAY_ENABLED", "version": "5.31.0" }, - { - "name": "TANGEM_PAY_ENTRYPOINT_ENABLED", - "version": "undefined" - }, { "name": "NEW_TOKEN_RECEIVE_ENABLED", "version": "5.28.0" diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml index 900691f421..f583c64401 100644 --- a/core/res/src/main/res/values/strings.xml +++ b/core/res/src/main/res/values/strings.xml @@ -1469,6 +1469,9 @@ Freeze Your card is frozen. Get Help + Reason: %s + %s ยท %s + MCC %s Other Unable to use on rooted devices Completed diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/Buttons.kt b/core/ui/src/main/java/com/tangem/core/ui/components/Buttons.kt index db823f8ad1..52caca9648 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/Buttons.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/Buttons.kt @@ -9,6 +9,7 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.material3.ButtonColors import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Shape import androidx.compose.ui.text.TextStyle import androidx.compose.ui.tooling.preview.Preview @@ -54,12 +55,16 @@ fun TextButtonIconStart( onClick: () -> Unit, modifier: Modifier = Modifier, colors: ButtonColors = TangemButtonsDefaults.defaultTextButtonColors, + tint: Color? = null, enabled: Boolean = true, ) { TangemButton( modifier = modifier, text = text, - icon = TangemButtonIconPosition.Start(iconResId), + icon = TangemButtonIconPosition.Start( + iconResId = iconResId, + iconTint = tint, + ), onClick = onClick, enabled = enabled, showProgress = false, @@ -117,13 +122,21 @@ fun PrimaryButtonIconEnd( onClick: () -> Unit, modifier: Modifier = Modifier, size: TangemButtonSize = TangemButtonSize.Default, + tint: Color? = null, showProgress: Boolean = false, enabled: Boolean = true, ) { TangemButton( modifier = modifier, text = text, - icon = if (iconResId != null) TangemButtonIconPosition.End(iconResId) else TangemButtonIconPosition.None, + icon = if (iconResId != null) { + TangemButtonIconPosition.End( + iconResId = iconResId, + iconTint = tint, + ) + } else { + TangemButtonIconPosition.None + }, onClick = onClick, colors = TangemButtonsDefaults.primaryButtonColors, enabled = enabled, @@ -145,11 +158,15 @@ fun PrimaryButtonIconEndTwoLines( showProgress: Boolean = false, enabled: Boolean = true, additionalText: String? = null, + tint: Color? = null, ) { TangemButton( modifier = modifier, text = text, - icon = TangemButtonIconPosition.End(iconResId), + icon = TangemButtonIconPosition.End( + iconResId = iconResId, + iconTint = tint, + ), onClick = onClick, colors = TangemButtonsDefaults.primaryButtonColors, enabled = enabled, @@ -171,13 +188,17 @@ fun PrimaryButtonIconStart( modifier: Modifier = Modifier, showProgress: Boolean = false, enabled: Boolean = true, + tint: Color? = null, size: TangemButtonSize = TangemButtonSize.Default, shape: Shape = size.toShape(), ) { TangemButton( modifier = modifier, text = text, - icon = TangemButtonIconPosition.Start(iconResId), + icon = TangemButtonIconPosition.Start( + iconResId = iconResId, + iconTint = tint, + ), onClick = onClick, colors = TangemButtonsDefaults.primaryButtonColors, enabled = enabled, @@ -225,13 +246,17 @@ fun SecondaryButtonIconEnd( modifier: Modifier = Modifier, showProgress: Boolean = false, enabled: Boolean = true, + tint: Color? = null, size: TangemButtonSize = TangemButtonSize.Default, shape: Shape = size.toShape(), ) { TangemButton( modifier = modifier, text = text, - icon = TangemButtonIconPosition.End(iconResId), + icon = TangemButtonIconPosition.End( + iconResId = iconResId, + iconTint = tint, + ), onClick = onClick, colors = TangemButtonsDefaults.secondaryButtonColors, enabled = enabled, @@ -253,13 +278,17 @@ fun SecondaryButtonIconStart( modifier: Modifier = Modifier, showProgress: Boolean = false, enabled: Boolean = true, + iconTint: Color? = null, size: TangemButtonSize = TangemButtonSize.Default, shape: Shape = size.toShape(), ) { TangemButton( modifier = modifier, text = text, - icon = TangemButtonIconPosition.Start(iconResId), + icon = TangemButtonIconPosition.Start( + iconResId = iconResId, + iconTint = iconTint, + ), onClick = onClick, colors = TangemButtonsDefaults.secondaryButtonColors, enabled = enabled, diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/buttons/common/TangemButton.kt b/core/ui/src/main/java/com/tangem/core/ui/components/buttons/common/TangemButton.kt index 4750445c40..302845e5ed 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/buttons/common/TangemButton.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/buttons/common/TangemButton.kt @@ -99,7 +99,7 @@ fun TangemButton( .padding(vertical = 2.dp) .testTag(BaseButtonTestTags.ICON), painter = painterResource(id = iconResId), - tint = colors.contentColor(enabled = enabled).value, + tint = icon.iconTint ?: colors.contentColor(enabled = enabled).value, contentDescription = null, ) }, diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/buttons/common/TangemButtonIconPosition.kt b/core/ui/src/main/java/com/tangem/core/ui/components/buttons/common/TangemButtonIconPosition.kt index 0e6169baa3..d4914922cb 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/buttons/common/TangemButtonIconPosition.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/buttons/common/TangemButtonIconPosition.kt @@ -2,17 +2,27 @@ package com.tangem.core.ui.components.buttons.common import androidx.annotation.DrawableRes import androidx.compose.runtime.Immutable +import androidx.compose.ui.graphics.Color @Immutable sealed interface TangemButtonIconPosition { val iconResId: Int? + val iconTint: Color? - data class Start(@DrawableRes override val iconResId: Int) : TangemButtonIconPosition + data class Start( + @DrawableRes override val iconResId: Int, + override val iconTint: Color? = null, + ) : TangemButtonIconPosition - data class End(@DrawableRes override val iconResId: Int) : TangemButtonIconPosition + data class End( + @DrawableRes override val iconResId: Int, + override val iconTint: Color? = null, + ) : TangemButtonIconPosition data object None : TangemButtonIconPosition { @DrawableRes override val iconResId: Int? = null + + override val iconTint: Color? = null } } \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/tokenlist/TokenListItem.kt b/core/ui/src/main/java/com/tangem/core/ui/components/tokenlist/TokenListItem.kt index 3b11421742..ddfbb831bf 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/tokenlist/TokenListItem.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/tokenlist/TokenListItem.kt @@ -17,7 +17,6 @@ import androidx.compose.ui.res.painterResource import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import com.tangem.core.ui.BuildConfig import com.tangem.core.ui.R import com.tangem.core.ui.components.SpacerW4 import com.tangem.core.ui.components.account.AccountCharIcon @@ -33,9 +32,9 @@ import com.tangem.core.ui.components.token.state.TokenItemState import com.tangem.core.ui.components.tokenlist.internal.GroupTitleItem import com.tangem.core.ui.components.tokenlist.state.PortfolioTokensListItemUM import com.tangem.core.ui.components.tokenlist.state.TokensListItemUM -import com.tangem.core.ui.extensions.conditional import com.tangem.core.ui.extensions.resolveReference import com.tangem.core.ui.res.TangemTheme +import com.tangem.core.ui.utils.ProvideSharedTransitionScope /** * Multi-currency content item @@ -69,7 +68,7 @@ fun TokenListItem(state: TokensListItemUM, isBalanceHidden: Boolean, modifier: M @Suppress("MagicNumber", "ReusedModifierInstance", "LongMethod") @Composable fun PortfolioListItem(state: TokensListItemUM.Portfolio, isBalanceHidden: Boolean, modifier: Modifier = Modifier) { - SharedTransitionLayout(modifier) { + ProvideSharedTransitionScope(modifier) { val iconSharedContentState = rememberSharedContentState(key = "icon_${state.id}") val titleSharedContentState = rememberSharedContentState(key = "title_${state.id}") val boundsTransform = BoundsTransform { _, _ -> tween(350) } @@ -101,13 +100,11 @@ fun PortfolioListItem(state: TokensListItemUM.Portfolio, isBalanceHidden: Boolea state = iconState, withFixedSize = false, modifier = modifier - .conditional(BuildConfig.DEBUG.not()) { - sharedBounds( - sharedContentState = iconSharedContentState, - animatedVisibilityScope = animatedContentScope, - boundsTransform = boundsTransform, - ) - }, + .sharedBounds( + sharedContentState = iconSharedContentState, + animatedVisibilityScope = animatedContentScope, + boundsTransform = boundsTransform, + ), ) }, title = { modifier: Modifier -> @@ -126,14 +123,12 @@ fun PortfolioListItem(state: TokensListItemUM.Portfolio, isBalanceHidden: Boolea state = state.tokenItemUM.titleState, textStyle = textStyle.copy(fontSize = textSize.sp), modifier = modifier - .conditional(BuildConfig.DEBUG.not()) { - sharedBounds( - sharedContentState = titleSharedContentState, - animatedVisibilityScope = animatedContentScope, - boundsTransform = boundsTransform, - resizeMode = scaleToBounds(ContentScale.Fit, Alignment.CenterStart), - ) - }, + .sharedBounds( + sharedContentState = titleSharedContentState, + animatedVisibilityScope = animatedContentScope, + boundsTransform = boundsTransform, + resizeMode = scaleToBounds(ContentScale.Fit, Alignment.CenterStart), + ), ) }, ) diff --git a/core/ui/src/main/java/com/tangem/core/ui/utils/DateTimeFormatters.kt b/core/ui/src/main/java/com/tangem/core/ui/utils/DateTimeFormatters.kt index 728da8c24c..50bee9b150 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/utils/DateTimeFormatters.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/utils/DateTimeFormatters.kt @@ -6,7 +6,6 @@ import com.tangem.core.ui.utils.DateTimeFormatters.dateMMMdd import com.tangem.core.ui.utils.DateTimeFormatters.dateTimeFormatter import com.tangem.core.ui.utils.DateTimeFormatters.dateYYYY import org.joda.time.DateTime -import org.joda.time.DateTimeZone import org.joda.time.format.DateTimeFormat import org.joda.time.format.DateTimeFormatter import org.joda.time.format.DateTimeFormatterBuilder @@ -18,7 +17,7 @@ object DateTimeFormatters { /** * Determine if the time is in 12-hour format ("10:00 PM") for the current locale. */ - val is12HourFormat by lazy { + private val is12HourFormat by lazy { /** * Two SS means, SHORT style for date and time. * If pattern contains "a", it means time is in 12 hour format. @@ -103,17 +102,15 @@ object DateTimeFormatters { * Local full date formatter (e.g., "dd MMMM, HH:mm") */ val localFullDate: DateTimeFormatter by lazy { + val locale = Locale.getDefault() + val datePattern = DateFormat.getBestDateTimePattern(locale, "dd MMMM") + val timeSkeleton = if (is12HourFormat) "h:mm a" else "HH:mm" + val timePattern = DateFormat.getBestDateTimePattern(locale, timeSkeleton) + val fullPattern = "$datePattern, $timePattern" DateTimeFormatterBuilder() - .appendDayOfMonth(2) - .appendLiteral(' ') - .appendMonthOfYearText() - .appendLiteral(", ") - .appendHourOfDay(2) - .appendLiteral(':') - .appendMinuteOfHour(2) + .appendPattern(fullPattern) .toFormatter() - .withLocale(Locale.getDefault()) - .withZone(DateTimeZone.getDefault()) + .withLocale(locale) } fun formatDate(date: DateTime, formatter: DateTimeFormatter = dateFormatter): String { diff --git a/core/ui/src/main/java/com/tangem/core/ui/utils/SharedTransitionUtils.kt b/core/ui/src/main/java/com/tangem/core/ui/utils/SharedTransitionUtils.kt new file mode 100644 index 0000000000..4851e2e34f --- /dev/null +++ b/core/ui/src/main/java/com/tangem/core/ui/utils/SharedTransitionUtils.kt @@ -0,0 +1,36 @@ +package com.tangem.core.ui.utils + +import androidx.compose.animation.SharedTransitionLayout +import androidx.compose.animation.SharedTransitionScope +import androidx.compose.foundation.layout.Box +import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.staticCompositionLocalOf +import androidx.compose.ui.Modifier + +@Composable +fun TangemSharedTransitionLayout( + modifier: Modifier = Modifier, + content: @Composable SharedTransitionScope.() -> Unit, +) { + SharedTransitionLayout(modifier) { + val sharedTransitionScope = this + CompositionLocalProvider( + LocalSharedTransitionScope provides sharedTransitionScope, + ) { + content() + } + } +} + +@Composable +fun ProvideSharedTransitionScope(modifier: Modifier = Modifier, content: @Composable SharedTransitionScope.() -> Unit) { + val sharedTransitionScope = LocalSharedTransitionScope.current + Box(modifier) { + sharedTransitionScope.content() + } +} + +private val LocalSharedTransitionScope = staticCompositionLocalOf { + error("No SharedTransitionScope provided") +} \ No newline at end of file diff --git a/core/ui/src/main/res/drawable/ic_heart_filled_20.xml b/core/ui/src/main/res/drawable/ic_heart_filled_20.xml new file mode 100644 index 0000000000..8578b94579 --- /dev/null +++ b/core/ui/src/main/res/drawable/ic_heart_filled_20.xml @@ -0,0 +1,11 @@ + + + diff --git a/data/account/src/main/kotlin/com/tangem/data/account/tokens/DefaultMainAccountTokensMigration.kt b/data/account/src/main/kotlin/com/tangem/data/account/tokens/DefaultMainAccountTokensMigration.kt index ce03285690..f03409232e 100644 --- a/data/account/src/main/kotlin/com/tangem/data/account/tokens/DefaultMainAccountTokensMigration.kt +++ b/data/account/src/main/kotlin/com/tangem/data/account/tokens/DefaultMainAccountTokensMigration.kt @@ -48,15 +48,17 @@ internal class DefaultMainAccountTokensMigration( exception } val mainAccount = findAccount(response = response, derivationIndex = DerivationIndex.Main) - val notMainAccounts = response.accounts + val customAccounts = response.accounts .filterNot { accountDTO -> accountDTO.derivationIndex.toDerivationIndex().isMain } - if (notMainAccounts.isEmpty()) { + if (customAccounts.isEmpty()) { Timber.i("There is only the Main account. Nothing to migrate") return@either response } + val customAccountIndexes = customAccounts.mapTo(hashSetOf(), WalletAccountDTO::derivationIndex) val unassignedTokens = mainAccount.groupUnassignedTokens() + .filter { it.key.value in customAccountIndexes } if (unassignedTokens.isEmpty()) { Timber.i("No unassigned tokens found for migration") @@ -64,7 +66,7 @@ internal class DefaultMainAccountTokensMigration( } var updatedMainAccount = mainAccount - val assignedTokensAccounts = notMainAccounts.mapNotNull { accountDTO -> + val assignedTokensAccounts = customAccounts.mapNotNull { accountDTO -> val derivationIndex = accountDTO.derivationIndex.toDerivationIndex() val tokensForAccount = unassignedTokens[derivationIndex] if (tokensForAccount.isNullOrEmpty()) return@mapNotNull null diff --git a/data/account/src/test/java/com/tangem/data/account/token/DefaultMainAccountTokensMigrationTest.kt b/data/account/src/test/java/com/tangem/data/account/token/DefaultMainAccountTokensMigrationTest.kt index e4d0df9606..150d3ec917 100644 --- a/data/account/src/test/java/com/tangem/data/account/token/DefaultMainAccountTokensMigrationTest.kt +++ b/data/account/src/test/java/com/tangem/data/account/token/DefaultMainAccountTokensMigrationTest.kt @@ -376,6 +376,7 @@ class DefaultMainAccountTokensMigrationTest { derivationIndex = DerivationIndex.Main.value, tokens = listOf( createBitcoin(accountIndex = 0), + createBitcoin(accountIndex = 10), ), ) diff --git a/data/news/src/main/java/com/tangem/data/news/repository/DefaultNewsRepository.kt b/data/news/src/main/java/com/tangem/data/news/repository/DefaultNewsRepository.kt index 84318ce9e7..3e3ef70c45 100644 --- a/data/news/src/main/java/com/tangem/data/news/repository/DefaultNewsRepository.kt +++ b/data/news/src/main/java/com/tangem/data/news/repository/DefaultNewsRepository.kt @@ -85,7 +85,7 @@ internal class DefaultNewsRepository( articlesToUpdate.map { article -> val isViewed = viewedFlags[article.id] == true article.copy(viewed = isViewed) - }.sortedBy { it.viewed } + } } .map, Either>> { it.right() } .catch { emit(it.left()) } diff --git a/data/transaction/src/main/java/com/tangem/data/transaction/DefaultGaslessTransactionRepository.kt b/data/transaction/src/main/java/com/tangem/data/transaction/DefaultGaslessTransactionRepository.kt index d01ad65f42..85eb924072 100644 --- a/data/transaction/src/main/java/com/tangem/data/transaction/DefaultGaslessTransactionRepository.kt +++ b/data/transaction/src/main/java/com/tangem/data/transaction/DefaultGaslessTransactionRepository.kt @@ -149,7 +149,7 @@ class DefaultGaslessTransactionRepository( } private companion object { - val BASE_GAS_FOR_TRANSACTION: BigInteger = BigInteger("100000") + val BASE_GAS_FOR_TRANSACTION: BigInteger = BigInteger("60000") val EMPTY_ADDRESSES = emptySet() } } \ No newline at end of file diff --git a/data/visa/src/main/kotlin/com/tangem/data/pay/DefaultTangemPayEligibilityManager.kt b/data/visa/src/main/kotlin/com/tangem/data/pay/DefaultTangemPayEligibilityManager.kt index 1f2acd1d9b..7225c10f60 100644 --- a/data/visa/src/main/kotlin/com/tangem/data/pay/DefaultTangemPayEligibilityManager.kt +++ b/data/visa/src/main/kotlin/com/tangem/data/pay/DefaultTangemPayEligibilityManager.kt @@ -9,7 +9,6 @@ import com.tangem.domain.pay.TangemPayEligibilityManager import com.tangem.domain.pay.repository.OnboardingRepository import com.tangem.domain.wallets.legacy.UserWalletsListManager import com.tangem.features.hotwallet.HotWalletFeatureToggles -import com.tangem.features.tangempay.TangemPayFeatureToggles import com.tangem.hot.sdk.model.HotWalletId import com.tangem.utils.coroutines.CoroutineDispatcherProvider import kotlinx.coroutines.* @@ -23,7 +22,6 @@ internal class DefaultTangemPayEligibilityManager @Inject constructor( private val userWalletsListManager: UserWalletsListManager, private val userWalletsListRepository: UserWalletsListRepository, private val hotWalletFeatureToggles: HotWalletFeatureToggles, - private val tangemPayFeatureToggles: TangemPayFeatureToggles, private val onboardingRepository: OnboardingRepository, ) : TangemPayEligibilityManager { @@ -128,8 +126,6 @@ internal class DefaultTangemPayEligibilityManager @Inject constructor( } private suspend fun checkTangemPayEligibility(): Boolean { - if (!tangemPayFeatureToggles.isEntryPointsEnabled) return true - return onboardingRepository.getCustomerEligibility() || onboardingRepository.checkCustomerEligibility() } diff --git a/data/visa/src/main/kotlin/com/tangem/data/pay/di/TangemPayDataModule.kt b/data/visa/src/main/kotlin/com/tangem/data/pay/di/TangemPayDataModule.kt index 93f2455ffb..719542728a 100644 --- a/data/visa/src/main/kotlin/com/tangem/data/pay/di/TangemPayDataModule.kt +++ b/data/visa/src/main/kotlin/com/tangem/data/pay/di/TangemPayDataModule.kt @@ -13,7 +13,6 @@ import com.tangem.domain.pay.usecase.TangemPayMainScreenCustomerInfoUseCase import com.tangem.domain.tangempay.GetTangemPayCurrencyStatusUseCase import com.tangem.domain.tangempay.TangemPayWithdrawUseCase import com.tangem.domain.tangempay.repository.TangemPayTxHistoryRepository -import com.tangem.features.tangempay.TangemPayFeatureToggles import com.tangem.security.DeviceSecurityInfoProvider import dagger.Binds import dagger.Module @@ -78,14 +77,12 @@ internal interface TangemPayDataModule { customerOrderRepository: CustomerOrderRepository, tangemPayOnboardingRepository: OnboardingRepository, eligibilityManager: TangemPayEligibilityManager, - tangemPayFeatureToggles: TangemPayFeatureToggles, deviceSecurity: DeviceSecurityInfoProvider, ): TangemPayMainScreenCustomerInfoUseCase { return TangemPayMainScreenCustomerInfoUseCase( onboardingRepository = repository, customerOrderRepository = customerOrderRepository, eligibilityManager = eligibilityManager, - tangemPayFeatureToggles = tangemPayFeatureToggles, deviceSecurity = deviceSecurity, ) } diff --git a/data/visa/src/main/kotlin/com/tangem/data/visa/utils/TangemPayTxHistoryItemConverter.kt b/data/visa/src/main/kotlin/com/tangem/data/visa/utils/TangemPayTxHistoryItemConverter.kt index b8ecc58913..23187867a8 100644 --- a/data/visa/src/main/kotlin/com/tangem/data/visa/utils/TangemPayTxHistoryItemConverter.kt +++ b/data/visa/src/main/kotlin/com/tangem/data/visa/utils/TangemPayTxHistoryItemConverter.kt @@ -46,6 +46,7 @@ internal class TangemPayTxHistoryItemConverter(moshi: Moshi) : merchantCategory = spend.merchantCategory, status = TangemPayTxHistoryItemStatusConverter.convert(spend.status), enrichedMerchantIconUrl = spend.enrichedMerchantIcon, + declinedReason = spend.declinedReason, ) } diff --git a/data/wallet-manager/src/main/java/com/tangem/data/walletmanager/DefaultWalletManagersFacade.kt b/data/wallet-manager/src/main/java/com/tangem/data/walletmanager/DefaultWalletManagersFacade.kt index d3937680e7..b2b04e77fa 100644 --- a/data/wallet-manager/src/main/java/com/tangem/data/walletmanager/DefaultWalletManagersFacade.kt +++ b/data/wallet-manager/src/main/java/com/tangem/data/walletmanager/DefaultWalletManagersFacade.kt @@ -280,6 +280,12 @@ internal class DefaultWalletManagersFacade @Inject constructor( ), ) + val gaslessFeeAddresses = try { + gaslessTransactionRepository.getGaslessFeeAddresses() + } catch (error: Throwable) { + Timber.e(error, "Failed to load gasless fee addresses; falling back to empty set") + emptySet() + } return when (itemsResult) { is Result.Success -> PaginationWrapper( currentPage = sdkPageConverter.convert(page), @@ -287,7 +293,7 @@ internal class DefaultWalletManagersFacade @Inject constructor( items = SdkTransactionHistoryItemConverter( smartContractMethods = readSmartContractMethods(), yieldSupplyAddresses = YIELD_SUPPLY_ADDRESSES, - gaslessFeeAddresses = gaslessTransactionRepository.getGaslessFeeAddresses(), + gaslessFeeAddresses = gaslessFeeAddresses, ).convertList(itemsResult.data.items), ) is Result.Failure -> error(itemsResult.error.message ?: itemsResult.error.customMessage) diff --git a/data/yield-supply/src/main/java/com/tangem/data/yield/supply/DefaultYieldSupplyRepository.kt b/data/yield-supply/src/main/java/com/tangem/data/yield/supply/DefaultYieldSupplyRepository.kt index 3565a65952..22f5b7a42e 100644 --- a/data/yield-supply/src/main/java/com/tangem/data/yield/supply/DefaultYieldSupplyRepository.kt +++ b/data/yield-supply/src/main/java/com/tangem/data/yield/supply/DefaultYieldSupplyRepository.kt @@ -27,9 +27,10 @@ import com.tangem.domain.yield.supply.models.YieldSupplyPendingStatus import com.tangem.domain.yield.supply.models.YieldSupplyMarketChartData import com.tangem.utils.coroutines.CoroutineDispatcherProvider import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.update import kotlinx.coroutines.withContext -import java.util.concurrent.ConcurrentHashMap internal class DefaultYieldSupplyRepository( private val yieldSupplyApi: YieldSupplyApi, @@ -40,7 +41,7 @@ internal class DefaultYieldSupplyRepository( private val appPreferencesStore: AppPreferencesStore, ) : YieldSupplyRepository { - private val statusMap: MutableMap = ConcurrentHashMap() + private val statusMapFlow = MutableStateFlow>(emptyMap()) override suspend fun getCachedMarkets(): List? = withContext(dispatchers.io) { val cache = store.getSyncOrNull().orEmpty() @@ -129,10 +130,12 @@ internal class DefaultYieldSupplyRepository( yieldSupplyPendingStatus: YieldSupplyPendingStatus?, ) { val key = getTokenProtocolStatusKey(userWalletId, cryptoCurrency) - if (yieldSupplyPendingStatus != null) { - statusMap[key] = yieldSupplyPendingStatus - } else { - statusMap.remove(key) + statusMapFlow.update { currentMap -> + if (yieldSupplyPendingStatus != null) { + currentMap + (key to yieldSupplyPendingStatus) + } else { + currentMap - key + } } } @@ -153,7 +156,15 @@ internal class DefaultYieldSupplyRepository( userWalletId: UserWalletId, cryptoCurrency: CryptoCurrency, ): YieldSupplyPendingStatus? { - return statusMap[getTokenProtocolStatusKey(userWalletId, cryptoCurrency)] + return statusMapFlow.value[getTokenProtocolStatusKey(userWalletId, cryptoCurrency)] + } + + override fun getTokenProtocolPendingStatusFlow( + userWalletId: UserWalletId, + cryptoCurrency: CryptoCurrency, + ): Flow { + val key = getTokenProtocolStatusKey(userWalletId, cryptoCurrency) + return statusMapFlow.map { it[key] } } private fun List.enrichNetworkIds(): List { diff --git a/domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/gasless/TokenFeeCalculator.kt b/domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/gasless/TokenFeeCalculator.kt index 41e65be9d6..cec62f3d5c 100644 --- a/domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/gasless/TokenFeeCalculator.kt +++ b/domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/gasless/TokenFeeCalculator.kt @@ -133,9 +133,10 @@ internal class TokenFeeCalculator( is Fee.Ethereum.TokenCurrency -> raiseIllegalStateError("initialFee could only be native") } - val feeInNativeCurrency = maxTokenFeeGas - .multiply(maxFeePerGas) - .multiply(GAS_PRICE_MULTIPLIER.toBigInteger()) + val feeInNativeCurrency = BigDecimal(maxTokenFeeGas.multiply(maxFeePerGas)) + .multiply(BigDecimal(GAS_PRICE_MULTIPLIER)) + .setScale(0, RoundingMode.UP) + .toBigInteger() val nativeFiatRate = nativeCurrencyStatus.value.fiatRate ?: raiseIllegalStateError("fiatRate is null") val tokenFiatRate = tokenForPayFeeStatus.value.fiatRate ?: raiseIllegalStateError("fiatRate is null") @@ -201,7 +202,7 @@ internal class TokenFeeCalculator( /** Amount in token units for fee transfer */ const val FEE_TRANSFER_AMOUNT = 0.01 // calculate using decimals /** Gas price safety multiplier for fee calculation */ - const val GAS_PRICE_MULTIPLIER = 2 + const val GAS_PRICE_MULTIPLIER = 1.5 const val PERCENT_TO_INCREASE_TOKEN_PRICE = 1 const val PERCENT_TO_INCREASE_TRANSFER_GASLIMIT = 10 diff --git a/domain/transaction/src/test/java/com/tangem/domain/transaction/usecase/gasless/TokenFeeCalculatorTest.kt b/domain/transaction/src/test/java/com/tangem/domain/transaction/usecase/gasless/TokenFeeCalculatorTest.kt index 3c60d6efea..704fa80538 100644 --- a/domain/transaction/src/test/java/com/tangem/domain/transaction/usecase/gasless/TokenFeeCalculatorTest.kt +++ b/domain/transaction/src/test/java/com/tangem/domain/transaction/usecase/gasless/TokenFeeCalculatorTest.kt @@ -363,7 +363,7 @@ class TokenFeeCalculatorTest { // Expected val expectedAmount = Amount( - value = BigDecimal("37.400000000000000000000000000000000000"), + value = BigDecimal("28.050000000000000000000000000000000000"), token = Token( name = "USDC", symbol = "USDC", diff --git a/domain/visa/models/src/main/kotlin/com/tangem/domain/visa/model/TangemPayTxHistoryItem.kt b/domain/visa/models/src/main/kotlin/com/tangem/domain/visa/model/TangemPayTxHistoryItem.kt index c272305d75..9741b0d593 100644 --- a/domain/visa/models/src/main/kotlin/com/tangem/domain/visa/model/TangemPayTxHistoryItem.kt +++ b/domain/visa/models/src/main/kotlin/com/tangem/domain/visa/model/TangemPayTxHistoryItem.kt @@ -29,6 +29,7 @@ sealed class TangemPayTxHistoryItem { val merchantCategory: String?, val status: Status, val enrichedMerchantIconUrl: String?, + val declinedReason: String?, ) : TangemPayTxHistoryItem() @Serializable diff --git a/domain/visa/src/main/kotlin/com/tangem/domain/pay/usecase/TangemPayMainScreenCustomerInfoUseCase.kt b/domain/visa/src/main/kotlin/com/tangem/domain/pay/usecase/TangemPayMainScreenCustomerInfoUseCase.kt index 33ddcff4c8..dfe593979e 100644 --- a/domain/visa/src/main/kotlin/com/tangem/domain/pay/usecase/TangemPayMainScreenCustomerInfoUseCase.kt +++ b/domain/visa/src/main/kotlin/com/tangem/domain/pay/usecase/TangemPayMainScreenCustomerInfoUseCase.kt @@ -9,7 +9,6 @@ import com.tangem.domain.pay.model.* import com.tangem.domain.pay.repository.CustomerOrderRepository import com.tangem.domain.pay.repository.OnboardingRepository import com.tangem.domain.visa.error.VisaApiError -import com.tangem.features.tangempay.TangemPayFeatureToggles import com.tangem.security.DeviceSecurityInfoProvider import com.tangem.security.isSecurityExposed import kotlinx.coroutines.flow.* @@ -21,7 +20,6 @@ class TangemPayMainScreenCustomerInfoUseCase( private val onboardingRepository: OnboardingRepository, private val customerOrderRepository: CustomerOrderRepository, private val eligibilityManager: TangemPayEligibilityManager, - private val tangemPayFeatureToggles: TangemPayFeatureToggles, private val deviceSecurity: DeviceSecurityInfoProvider, ) { @@ -44,7 +42,7 @@ class TangemPayMainScreenCustomerInfoUseCase( .fold( ifLeft = { error -> Timber.tag(TAG).e("Failed checkCustomerWallet for $userWalletId: ${error.javaClass.simpleName}") - if (error is VisaApiError.NotPaeraCustomer && tangemPayFeatureToggles.isEntryPointsEnabled) { + if (error is VisaApiError.NotPaeraCustomer) { showOnboardingBannerIfEligible(userWalletId) } else { updateState(userWalletId, TangemPayCustomerInfoError.UnknownError.left()) @@ -62,13 +60,8 @@ class TangemPayMainScreenCustomerInfoUseCase( .map(MainCustomerInfoContentState::Content) updateState(userWalletId, result) } else { - if (tangemPayFeatureToggles.isEntryPointsEnabled) { - // if there's no tangem pay, check eligibility and show onboarding banner - showOnboardingBannerIfEligible(userWalletId) - } else { - // ignore if there's no TangemPay - updateState(userWalletId, TangemPayCustomerInfoError.UnknownError.left()) - } + // if there's no tangem pay, check eligibility and show onboarding banner + showOnboardingBannerIfEligible(userWalletId) } }, ) diff --git a/domain/yield-supply/src/main/java/com/tangem/domain/yield/supply/YieldSupplyRepository.kt b/domain/yield-supply/src/main/java/com/tangem/domain/yield/supply/YieldSupplyRepository.kt index c8841c32c5..19dd17d7ee 100644 --- a/domain/yield-supply/src/main/java/com/tangem/domain/yield/supply/YieldSupplyRepository.kt +++ b/domain/yield-supply/src/main/java/com/tangem/domain/yield/supply/YieldSupplyRepository.kt @@ -104,6 +104,18 @@ interface YieldSupplyRepository { cryptoCurrency: CryptoCurrency, ): YieldSupplyPendingStatus? + /** + * Observe the pending status for the given wallet and currency as a [Flow]. + * + * @param userWalletId the wallet to observe + * @param cryptoCurrency the currency or token to observe + * @return a [Flow] emitting the current pending status or null if none exists + */ + fun getTokenProtocolPendingStatusFlow( + userWalletId: UserWalletId, + cryptoCurrency: CryptoCurrency, + ): Flow + fun getShouldShowYieldPromoBanner(): Flow suspend fun setShouldShowYieldPromoBanner(shouldShow: Boolean) diff --git a/domain/yield-supply/src/main/java/com/tangem/domain/yield/supply/usecase/YieldSupplyEnterStatusFlowUseCase.kt b/domain/yield-supply/src/main/java/com/tangem/domain/yield/supply/usecase/YieldSupplyEnterStatusFlowUseCase.kt new file mode 100644 index 0000000000..094b852d20 --- /dev/null +++ b/domain/yield-supply/src/main/java/com/tangem/domain/yield/supply/usecase/YieldSupplyEnterStatusFlowUseCase.kt @@ -0,0 +1,16 @@ +package com.tangem.domain.yield.supply.usecase + +import com.tangem.domain.models.currency.CryptoCurrency +import com.tangem.domain.models.wallet.UserWalletId +import com.tangem.domain.yield.supply.YieldSupplyRepository +import com.tangem.domain.yield.supply.models.YieldSupplyPendingStatus +import kotlinx.coroutines.flow.Flow + +class YieldSupplyEnterStatusFlowUseCase( + private val yieldSupplyRepository: YieldSupplyRepository, +) { + + operator fun invoke(userWalletId: UserWalletId, cryptoCurrency: CryptoCurrency): Flow { + return yieldSupplyRepository.getTokenProtocolPendingStatusFlow(userWalletId, cryptoCurrency) + } +} \ No newline at end of file diff --git a/domain/yield-supply/src/main/java/com/tangem/domain/yield/supply/usecase/YieldSupplyEnterStatusUseCase.kt b/domain/yield-supply/src/main/java/com/tangem/domain/yield/supply/usecase/YieldSupplyEnterStatusUseCase.kt index 07ea5dabf4..bb6bec283f 100644 --- a/domain/yield-supply/src/main/java/com/tangem/domain/yield/supply/usecase/YieldSupplyEnterStatusUseCase.kt +++ b/domain/yield-supply/src/main/java/com/tangem/domain/yield/supply/usecase/YieldSupplyEnterStatusUseCase.kt @@ -24,28 +24,24 @@ class YieldSupplyEnterStatusUseCase( .toSet() val hasPendingTx = status?.txIds?.any { it in pendingTxHashes } == true - if (hasPendingTx) { - status + val isActive = cryptoCurrencyStatus.value.yieldSupplyStatus?.isActive == true + val isExpired = status != null && + System.currentTimeMillis() - status.createdAt > STATUS_EXPIRATION_MS + val shouldClearStatus = when { + isActive && status is YieldSupplyPendingStatus.Enter -> true + !isActive && status is YieldSupplyPendingStatus.Exit -> true + isExpired && !hasPendingTx -> true + else -> false + } + if (shouldClearStatus) { + yieldSupplyRepository.saveTokenProtocolPendingStatus( + userWalletId, + cryptoCurrencyStatus.currency, + null, + ) + null } else { - val isActive = cryptoCurrencyStatus.value.yieldSupplyStatus?.isActive == true - val isExpired = status != null && - System.currentTimeMillis() - status.createdAt > STATUS_EXPIRATION_MS - val shouldClearStatus = when { - isExpired -> true - isActive && status is YieldSupplyPendingStatus.Exit -> false - !isActive && status is YieldSupplyPendingStatus.Enter -> false - else -> true - } - if (shouldClearStatus) { - yieldSupplyRepository.saveTokenProtocolPendingStatus( - userWalletId, - cryptoCurrencyStatus.currency, - null, - ) - null - } else { - status - } + status } } } diff --git a/domain/yield-supply/src/test/java/com/tangem/domain/yield/supply/usecase/YieldSupplyEnterStatusUseCaseTest.kt b/domain/yield-supply/src/test/java/com/tangem/domain/yield/supply/usecase/YieldSupplyEnterStatusUseCaseTest.kt index 7d5e4ec84d..d3f2581274 100644 --- a/domain/yield-supply/src/test/java/com/tangem/domain/yield/supply/usecase/YieldSupplyEnterStatusUseCaseTest.kt +++ b/domain/yield-supply/src/test/java/com/tangem/domain/yield/supply/usecase/YieldSupplyEnterStatusUseCaseTest.kt @@ -91,16 +91,13 @@ class YieldSupplyEnterStatusUseCaseTest { coEvery { yieldSupplyRepository.getPendingTxHashes(userWalletId, cryptoStatus.currency) } returns emptyList() - coEvery { - yieldSupplyRepository.saveTokenProtocolPendingStatus(userWalletId, token, null) - } returns Unit val result = useCase(userWalletId, cryptoStatus) assertThat(result.isRight()).isTrue() val value = (result as Either.Right).value assertThat(value).isNull() - coVerify { yieldSupplyRepository.saveTokenProtocolPendingStatus(userWalletId, token, null) } + coVerify(exactly = 0) { yieldSupplyRepository.saveTokenProtocolPendingStatus(any(), any(), any()) } } @Test @@ -147,7 +144,7 @@ class YieldSupplyEnterStatusUseCaseTest { @Test fun `GIVEN exit status with pending tx WHEN invoke THEN returns status`() = runTest { val token = createToken() - val cryptoStatus = createStatus(token) + val cryptoStatus = createStatus(token, isActive = true) val pendingTxHash = "0xexit456" val status = YieldSupplyPendingStatus.Exit(txIds = listOf(pendingTxHash)) diff --git a/features/details/impl/src/main/kotlin/com/tangem/features/details/model/DetailsModel.kt b/features/details/impl/src/main/kotlin/com/tangem/features/details/model/DetailsModel.kt index 9d316e6c90..4abde012d5 100644 --- a/features/details/impl/src/main/kotlin/com/tangem/features/details/model/DetailsModel.kt +++ b/features/details/impl/src/main/kotlin/com/tangem/features/details/model/DetailsModel.kt @@ -35,7 +35,6 @@ import com.tangem.features.details.entity.SelectEmailFeedbackTypeBS import com.tangem.features.details.utils.ItemsBuilder import com.tangem.features.details.utils.SocialsBuilder import com.tangem.features.hotwallet.HotWalletFeatureToggles -import com.tangem.features.tangempay.TangemPayFeatureToggles import com.tangem.utils.coroutines.CoroutineDispatcherProvider import com.tangem.utils.version.AppVersionProvider import kotlinx.collections.immutable.ImmutableList @@ -71,7 +70,6 @@ internal class DetailsModel @Inject constructor( private val hotWalletFeatureToggles: HotWalletFeatureToggles, private val analyticsEventHandler: AnalyticsEventHandler, private val tangemPayEligibilityManager: TangemPayEligibilityManager, - private val tangemPayFeatureToggles: TangemPayFeatureToggles, ) : Model() { private val params: DetailsComponent.Params = paramsContainer.require() @@ -249,7 +247,6 @@ internal class DetailsModel @Inject constructor( } private fun addTangemPayItemIfEligible() { - if (!tangemPayFeatureToggles.isEntryPointsEnabled) return modelScope.launch { val isEligible = tangemPayEligibilityManager .getEligibleWallets(shouldExcludePaeraCustomers = true) diff --git a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/ui/news/details/NewsDetailsContent.kt b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/ui/news/details/NewsDetailsContent.kt index c1ba03727c..41d152a94b 100644 --- a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/ui/news/details/NewsDetailsContent.kt +++ b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/ui/news/details/NewsDetailsContent.kt @@ -16,15 +16,20 @@ import androidx.compose.material3.Icon import androidx.compose.material3.Text import androidx.compose.material3.VerticalDivider import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember import androidx.compose.runtime.snapshotFlow import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color +import androidx.compose.ui.hapticfeedback.HapticFeedbackType import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.platform.LocalHapticFeedback import androidx.compose.ui.res.painterResource import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview @@ -141,6 +146,7 @@ private fun ArticleDetail( relatedTokensUM: RelatedTokensUM, modifier: Modifier = Modifier, ) { + val hapticFeedback = LocalHapticFeedback.current val density = LocalDensity.current val background = LocalMainBottomSheetColor.current.value val pagerHeight = 32.dp @@ -184,23 +190,21 @@ private fun ArticleDetail( SpacerH(24.dp) - if (article.isLiked) { - PrimaryButtonIconStart( - modifier = Modifier.padding(horizontal = 16.dp), - iconResId = R.drawable.ic_heart_20, - text = stringResourceSafe(R.string.news_like), - size = TangemButtonSize.RoundedAction, - onClick = { onLikeClick() }, - ) - } else { - SecondaryButtonIconStart( - modifier = Modifier.padding(horizontal = 16.dp), - iconResId = R.drawable.ic_heart_20, - text = stringResourceSafe(R.string.news_like), - size = TangemButtonSize.RoundedAction, - onClick = { onLikeClick() }, - ) - } + SecondaryButtonIconStart( + modifier = Modifier.padding(horizontal = 16.dp), + iconResId = if (article.isLiked) { + R.drawable.ic_heart_filled_20 + } else { + R.drawable.ic_heart_20 + }, + iconTint = Color.Unspecified, + text = stringResourceSafe(R.string.news_like), + size = TangemButtonSize.RoundedAction, + onClick = { + hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress) + onLikeClick() + }, + ) RelatedTokensBlock( relatedTokensUM = relatedTokensUM, @@ -375,16 +379,21 @@ private fun RelatedNewsItem(relatedArticle: RelatedArticleUM, modifier: Modifier @Composable private fun PreviewNewsDetailsContent() { TangemThemePreview { - NewsDetailsContent( - state = NewsDetailsUM( - articlesStateUM = ArticlesStateUM.Content, - articles = MockArticlesFactory.createMockArticles(), - selectedArticleIndex = 0, - onShareClick = {}, - onLikeClick = {}, - onBackClick = {}, - onArticleIndexChanged = {}, - ), - ) + val background = TangemTheme.colors.background.tertiary + CompositionLocalProvider( + LocalMainBottomSheetColor provides remember { mutableStateOf(background) }, + ) { + NewsDetailsContent( + state = NewsDetailsUM( + articlesStateUM = ArticlesStateUM.Content, + articles = MockArticlesFactory.createMockArticles(), + selectedArticleIndex = 0, + onShareClick = {}, + onLikeClick = {}, + onBackClick = {}, + onArticleIndexChanged = {}, + ), + ) + } } } \ No newline at end of file diff --git a/features/send-v2/api/src/main/java/com/tangem/features/send/v2/api/analytics/CommonSendAnalyticEvents.kt b/features/send-v2/api/src/main/java/com/tangem/features/send/v2/api/analytics/CommonSendAnalyticEvents.kt index ca4a28c1c6..597de0c5d3 100644 --- a/features/send-v2/api/src/main/java/com/tangem/features/send/v2/api/analytics/CommonSendAnalyticEvents.kt +++ b/features/send-v2/api/src/main/java/com/tangem/features/send/v2/api/analytics/CommonSendAnalyticEvents.kt @@ -44,22 +44,30 @@ sealed class CommonSendAnalyticEvents( data class FeeScreenOpened( val categoryName: String, val source: CommonSendSource, + val blockchain: String, + val token: String, ) : CommonSendAnalyticEvents( category = categoryName, event = "Fee Screen Opened", params = mapOf( SOURCE to source.analyticsName, + BLOCKCHAIN to blockchain, + TOKEN_PARAM to token, ), ) data class FeeSummaryScreenOpened( val categoryName: String, val source: CommonSendSource, + val blockchain: String, + val token: String, ) : CommonSendAnalyticEvents( category = categoryName, event = "Fee Summary Screen Opened", params = mapOf( SOURCE to source.analyticsName, + BLOCKCHAIN to blockchain, + TOKEN_PARAM to token, ), ) @@ -67,12 +75,14 @@ sealed class CommonSendAnalyticEvents( val categoryName: String, val source: CommonSendSource, val availableTokens: String, + val blockchain: String, ) : CommonSendAnalyticEvents( category = categoryName, event = "Fee Token Screen Opened", params = mapOf( SOURCE to source.analyticsName, "Available Fee" to availableTokens, + BLOCKCHAIN to blockchain, ), ) diff --git a/features/send-v2/api/src/main/java/com/tangem/features/send/v2/api/subcomponents/feeSelector/analytics/CommonSendFeeAnalyticEvents.kt b/features/send-v2/api/src/main/java/com/tangem/features/send/v2/api/subcomponents/feeSelector/analytics/CommonSendFeeAnalyticEvents.kt index 1daa74b204..e91e35e365 100644 --- a/features/send-v2/api/src/main/java/com/tangem/features/send/v2/api/subcomponents/feeSelector/analytics/CommonSendFeeAnalyticEvents.kt +++ b/features/send-v2/api/src/main/java/com/tangem/features/send/v2/api/subcomponents/feeSelector/analytics/CommonSendFeeAnalyticEvents.kt @@ -6,6 +6,7 @@ import com.tangem.core.analytics.models.AnalyticsParam.Key.BLOCKCHAIN import com.tangem.core.analytics.models.AnalyticsParam.Key.FEE_TOKEN import com.tangem.core.analytics.models.AnalyticsParam.Key.FEE_TYPE import com.tangem.core.analytics.models.AnalyticsParam.Key.SOURCE +import com.tangem.core.analytics.models.AnalyticsParam.Key.TOKEN_PARAM import com.tangem.features.send.v2.api.analytics.CommonSendAnalyticEvents.CommonSendSource sealed class CommonSendFeeAnalyticEvents( @@ -38,11 +39,13 @@ sealed class CommonSendFeeAnalyticEvents( data class CustomFeeButtonClicked( override val categoryName: String, val blockchain: String, + val token: String, ) : CommonSendFeeAnalyticEvents( category = categoryName, event = "Custom Fee Clicked", params = mapOf( BLOCKCHAIN to blockchain, + TOKEN_PARAM to token, ), ) diff --git a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/feeselector/model/FeeSelectorLogic.kt b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/feeselector/model/FeeSelectorLogic.kt index 4b738f288d..b9f62b2008 100644 --- a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/feeselector/model/FeeSelectorLogic.kt +++ b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/feeselector/model/FeeSelectorLogic.kt @@ -40,6 +40,7 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch +import timber.log.Timber @Suppress("LongParameterList") internal class FeeSelectorLogic @AssistedInject constructor( @@ -122,6 +123,7 @@ internal class FeeSelectorLogic @AssistedInject constructor( CommonSendFeeAnalyticEvents.CustomFeeButtonClicked( categoryName = params.analyticsCategoryName, blockchain = params.cryptoCurrencyStatus.currency.network.name, + token = params.cryptoCurrencyStatus.currency.symbol, ), ) } @@ -265,10 +267,21 @@ internal class FeeSelectorLogic @AssistedInject constructor( private suspend fun populateExtendedFee( fee: TransactionFeeExtended, ): Either = either { + val selectedToken = getSelectedTokenStatus(fee.feeTokenId).bind() + val availableTokens = getAvailableFeeTokens().fold( + ifLeft = { error -> + Timber.e("Failed to get available fee tokens: $error") + if (selectedToken.currency !is CryptoCurrency.Coin) { + raise(error) + } + emptyList() + }, + ifRight = { it }, + ) LoadedFeeResult.Extended( fee = fee, - selectedToken = getSelectedTokenStatus(fee.feeTokenId).bind(), - availableTokens = getAvailableFeeTokens().bind(), + selectedToken = selectedToken, + availableTokens = availableTokens, ) } diff --git a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/feeselector/model/FeeSelectorModel.kt b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/feeselector/model/FeeSelectorModel.kt index f4abb899d9..52d5766642 100644 --- a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/feeselector/model/FeeSelectorModel.kt +++ b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/feeselector/model/FeeSelectorModel.kt @@ -86,6 +86,8 @@ internal class FeeSelectorModel @Inject constructor( CommonSendAnalyticEvents.FeeSummaryScreenOpened( categoryName = params.analyticsCategoryName, source = params.analyticsSendSource, + blockchain = params.cryptoCurrencyStatus.currency.network.name, + token = params.cryptoCurrencyStatus.currency.symbol, ), ) } @@ -95,6 +97,8 @@ internal class FeeSelectorModel @Inject constructor( CommonSendAnalyticEvents.FeeScreenOpened( categoryName = params.analyticsCategoryName, source = params.analyticsSendSource, + blockchain = params.cryptoCurrencyStatus.currency.network.name, + token = params.cryptoCurrencyStatus.currency.symbol, ), ) } @@ -105,6 +109,7 @@ internal class FeeSelectorModel @Inject constructor( categoryName = params.analyticsCategoryName, source = params.analyticsSendSource, availableTokens = getAvailableTokensString(), + blockchain = params.cryptoCurrencyStatus.currency.network.name, ), ) } diff --git a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/send/analytics/SendAnalyticEvents.kt b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/send/analytics/SendAnalyticEvents.kt index e837d589cf..eab26f440b 100644 --- a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/send/analytics/SendAnalyticEvents.kt +++ b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/send/analytics/SendAnalyticEvents.kt @@ -5,6 +5,7 @@ import com.tangem.core.analytics.models.AnalyticsParam import com.tangem.core.analytics.models.AnalyticsParam.Key.ACCOUNT_DERIVATION_FROM import com.tangem.core.analytics.models.AnalyticsParam.Key.BLOCKCHAIN import com.tangem.core.analytics.models.AnalyticsParam.Key.ENS_ADDRESS +import com.tangem.core.analytics.models.AnalyticsParam.Key.FEE_TOKEN import com.tangem.core.analytics.models.AnalyticsParam.Key.FEE_TYPE import com.tangem.core.analytics.models.AnalyticsParam.Key.NONCE import com.tangem.core.analytics.models.AnalyticsParam.Key.TOKEN_PARAM @@ -42,6 +43,7 @@ internal sealed class SendAnalyticEvents( AnalyticsParam.EmptyFull.Full -> true.toString().capitalize() } put(ENS_ADDRESS, ensAddress) + put(FEE_TOKEN, feeToken) }, ), AppsFlyerIncludedEvent diff --git a/features/swap/impl/src/main/java/com/tangem/feature/swap/analytics/SwapEvents.kt b/features/swap/impl/src/main/java/com/tangem/feature/swap/analytics/SwapEvents.kt index 1e0c855abc..a2b2f3ea67 100644 --- a/features/swap/impl/src/main/java/com/tangem/feature/swap/analytics/SwapEvents.kt +++ b/features/swap/impl/src/main/java/com/tangem/feature/swap/analytics/SwapEvents.kt @@ -2,12 +2,14 @@ package com.tangem.feature.swap.analytics import com.tangem.common.ui.bottomsheet.permission.state.ApproveType import com.tangem.core.analytics.models.AnalyticsEvent +import com.tangem.core.analytics.models.AnalyticsParam.Key.BLOCKCHAIN import com.tangem.core.analytics.models.AnalyticsParam.Key.ERROR_CODE import com.tangem.core.analytics.models.AnalyticsParam.Key.ERROR_MESSAGE import com.tangem.core.analytics.models.AnalyticsParam.Key.FEE_TOKEN import com.tangem.core.analytics.models.AnalyticsParam.Key.PROVIDER import com.tangem.core.analytics.models.AnalyticsParam.Key.RECEIVE_TOKEN import com.tangem.core.analytics.models.AnalyticsParam.Key.SEND_TOKEN +import com.tangem.core.analytics.models.AnalyticsParam.Key.TOKEN_PARAM import com.tangem.core.analytics.models.AppsFlyerIncludedEvent import com.tangem.feature.swap.domain.models.domain.SwapProvider import com.tangem.feature.swap.domain.models.ui.FeeType @@ -20,9 +22,15 @@ sealed class SwapEvents( params: Map = emptyMap(), ) : AnalyticsEvent(SWAP_CATEGORY, event, params) { - data class SwapScreenOpened(val token: String) : SwapEvents( + data class SwapScreenOpened( + val token: String, + val blockchain: String, + ) : SwapEvents( event = "Swap Screen Opened", - params = mapOf("Token" to token), + params = mapOf( + TOKEN_PARAM to token, + BLOCKCHAIN to blockchain, + ), ), AppsFlyerIncludedEvent class SendTokenBalanceClicked : SwapEvents(event = "Send Token Balance Clicked") diff --git a/features/swap/impl/src/main/java/com/tangem/feature/swap/model/SwapModel.kt b/features/swap/impl/src/main/java/com/tangem/feature/swap/model/SwapModel.kt index be6829e703..6ae4815817 100644 --- a/features/swap/impl/src/main/java/com/tangem/feature/swap/model/SwapModel.kt +++ b/features/swap/impl/src/main/java/com/tangem/feature/swap/model/SwapModel.kt @@ -330,7 +330,12 @@ internal class SwapModel @Inject constructor( } } - analyticsEventHandler.send(SwapEvents.SwapScreenOpened(initialCurrencyFrom.symbol)) + analyticsEventHandler.send( + SwapEvents.SwapScreenOpened( + token = initialCurrencyFrom.symbol, + blockchain = initialCurrencyFrom.network.name, + ), + ) getBalanceHidingSettingsUseCase() .onEach { settings -> diff --git a/features/tangempay/details/api/src/main/kotlin/com/tangem/features/tangempay/TangemPayFeatureToggles.kt b/features/tangempay/details/api/src/main/kotlin/com/tangem/features/tangempay/TangemPayFeatureToggles.kt index 0345c3cb32..393e589bce 100644 --- a/features/tangempay/details/api/src/main/kotlin/com/tangem/features/tangempay/TangemPayFeatureToggles.kt +++ b/features/tangempay/details/api/src/main/kotlin/com/tangem/features/tangempay/TangemPayFeatureToggles.kt @@ -2,5 +2,4 @@ package com.tangem.features.tangempay interface TangemPayFeatureToggles { val isTangemPayEnabled: Boolean - val isEntryPointsEnabled: Boolean } \ No newline at end of file diff --git a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/DefaultTangemPayFeatureToggles.kt b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/DefaultTangemPayFeatureToggles.kt index 9b909186a0..a51c11a3bc 100644 --- a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/DefaultTangemPayFeatureToggles.kt +++ b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/DefaultTangemPayFeatureToggles.kt @@ -7,6 +7,4 @@ internal class DefaultTangemPayFeatureToggles( ) : TangemPayFeatureToggles { override val isTangemPayEnabled get() = featureTogglesManager.isFeatureEnabled("TANGEM_PAY_ENABLED") - override val isEntryPointsEnabled: Boolean - get() = featureTogglesManager.isFeatureEnabled("TANGEM_PAY_ENTRYPOINT_ENABLED") } \ No newline at end of file diff --git a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/entity/TangemPayTxHistoryDetailsUM.kt b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/entity/TangemPayTxHistoryDetailsUM.kt index 4d2aa7c66e..b240e9c159 100644 --- a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/entity/TangemPayTxHistoryDetailsUM.kt +++ b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/entity/TangemPayTxHistoryDetailsUM.kt @@ -17,10 +17,17 @@ internal data class TangemPayTxHistoryDetailsUM( val transactionAmountColor: ColorReference, val localTransactionText: String?, val labelState: LabelUM?, - val notification: NotificationConfig?, + val notification: NotificationState?, val buttons: ImmutableList, val dismiss: () -> Unit, ) { + data class NotificationState( + val config: NotificationConfig, + val titleColor: ColorReference, + val iconTint: ColorReference, + val containerColor: ColorReference?, + ) + data class ButtonState(val text: TextReference, val onClick: () -> Unit, val startIcon: ImageReference.Res? = null) } \ No newline at end of file diff --git a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/model/transformers/PayDetailsSpendSubtitleConverter.kt b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/model/transformers/PayDetailsSpendSubtitleConverter.kt new file mode 100644 index 0000000000..26f54a975c --- /dev/null +++ b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/model/transformers/PayDetailsSpendSubtitleConverter.kt @@ -0,0 +1,46 @@ +package com.tangem.features.tangempay.model.transformers + +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.domain.visa.model.TangemPayTxHistoryItem +import com.tangem.features.tangempay.details.impl.R +import com.tangem.utils.converter.Converter + +internal object PayDetailsSpendSubtitleConverter : Converter { + + override fun convert(value: TangemPayTxHistoryItem.Spend): TextReference { + val merchantCategory = value.merchantCategory + val enrichedMerchantCategory = value.enrichedMerchantCategory + val merchantCategoryCode = value.merchantCategoryCode + + val categoryCodeText = if (!merchantCategoryCode.isNullOrEmpty()) { + resourceReference( + id = R.string.tangem_pay_history_item_spend_mcc, + formatArgs = wrappedList(merchantCategoryCode), + ) + } else { + null + } + + val categoryText = when { + // if merchantCategory isNotEmpty use this + !merchantCategory.isNullOrEmpty() -> stringReference(merchantCategory) + + // If merchantCategory empty or null but enrichedMerchantCategory is not empty + !enrichedMerchantCategory.isNullOrEmpty() -> stringReference(enrichedMerchantCategory) + + else -> resourceReference(R.string.tangem_pay_other) + } + + return if (categoryCodeText != null) { + resourceReference( + id = R.string.tangem_pay_history_item_spend_mc_title_format, + formatArgs = wrappedList(categoryText, categoryCodeText), + ) + } else { + categoryText + } + } +} \ No newline at end of file diff --git a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/model/transformers/TangemPayTxHistoryDetailsConverter.kt b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/model/transformers/TangemPayTxHistoryDetailsConverter.kt index 28706d2143..8269d2423a 100644 --- a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/model/transformers/TangemPayTxHistoryDetailsConverter.kt +++ b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/model/transformers/TangemPayTxHistoryDetailsConverter.kt @@ -7,6 +7,7 @@ import com.tangem.core.ui.extensions.* import com.tangem.core.ui.format.bigdecimal.fiat import com.tangem.core.ui.format.bigdecimal.format import com.tangem.core.ui.format.bigdecimal.price +import com.tangem.core.ui.res.TangemColorPalette import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.utils.DateTimeFormatters import com.tangem.domain.visa.model.TangemPayTxHistoryItem @@ -24,7 +25,7 @@ internal object TangemPayTxHistoryDetailsConverter : Converter { private val dateFormatter = DateTimeFormatters.getBestFormatterBySkeleton("dd MMMM") - private val paySpendSubtitleConverter = PaySpendSubtitleConverter + private val paySpendSubtitleConverter = PayDetailsSpendSubtitleConverter override fun convert(value: Input): TangemPayTxHistoryDetailsUM { val transaction = value.item @@ -140,13 +141,16 @@ internal object TangemPayTxHistoryDetailsConverter : private fun TangemPayTxHistoryItem.extractAmountColor(): ColorReference { return when (this) { is TangemPayTxHistoryItem.Fee, - is TangemPayTxHistoryItem.Spend, is TangemPayTxHistoryItem.Payment, -> themedColor { TangemTheme.colors.text.primary1 } is TangemPayTxHistoryItem.Collateral -> when (this.type) { TangemPayTxHistoryItem.Type.Deposit -> themedColor { TangemTheme.colors.text.accent } TangemPayTxHistoryItem.Type.Withdrawal -> themedColor { TangemTheme.colors.text.primary1 } } + is TangemPayTxHistoryItem.Spend -> when (this.status) { + TangemPayTxHistoryItem.Status.DECLINED -> themedColor { TangemTheme.colors.text.warning } + else -> themedColor { TangemTheme.colors.text.primary1 } + } } } @@ -200,21 +204,39 @@ internal object TangemPayTxHistoryDetailsConverter : } } - private fun TangemPayTxHistoryItem.extractNotification(): NotificationConfig? { + private fun TangemPayTxHistoryItem.extractNotification(): TangemPayTxHistoryDetailsUM.NotificationState? { return when (this) { is TangemPayTxHistoryItem.Payment -> null is TangemPayTxHistoryItem.Collateral -> null - is TangemPayTxHistoryItem.Fee -> NotificationConfig( - title = resourceReference(R.string.tangem_pay_transaction_fee_notification_text), - subtitle = TextReference.EMPTY, - iconResId = R.drawable.ic_token_info_24, - ) - is TangemPayTxHistoryItem.Spend -> when (this.status) { - TangemPayTxHistoryItem.Status.DECLINED -> NotificationConfig( - title = resourceReference(R.string.tangem_pay_transaction_declined_notification_text), + is TangemPayTxHistoryItem.Fee -> TangemPayTxHistoryDetailsUM.NotificationState( + config = NotificationConfig( + title = resourceReference(R.string.tangem_pay_transaction_fee_notification_text), subtitle = TextReference.EMPTY, iconResId = R.drawable.ic_token_info_24, - ) + ), + titleColor = themedColor { TangemTheme.colors.text.tertiary }, + iconTint = themedColor { TangemTheme.colors.icon.secondary }, + containerColor = null, + ) + is TangemPayTxHistoryItem.Spend -> when (this.status) { + TangemPayTxHistoryItem.Status.DECLINED -> + TangemPayTxHistoryDetailsUM.NotificationState( + config = NotificationConfig( + title = if (declinedReason.isNullOrEmpty()) { + resourceReference(R.string.tangem_pay_transaction_declined_notification_text) + } else { + resourceReference( + id = R.string.tangem_pay_history_item_spend_mc_declined_reason, + formatArgs = wrappedList(requireNotNull(declinedReason)), + ) + }, + subtitle = TextReference.EMPTY, + iconResId = R.drawable.ic_token_info_24, + ), + titleColor = themedColor { TangemTheme.colors.text.warning }, + iconTint = themedColor { TangemTheme.colors.icon.warning }, + containerColor = themedColor { TangemColorPalette.Amaranth.copy(alpha = 0.1F) }, + ) TangemPayTxHistoryItem.Status.PENDING, TangemPayTxHistoryItem.Status.COMPLETED, TangemPayTxHistoryItem.Status.RESERVED, diff --git a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/model/transformers/TangemPayTxHistoryItemsConverter.kt b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/model/transformers/TangemPayTxHistoryItemsConverter.kt index a546d5abea..bf0f79c163 100644 --- a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/model/transformers/TangemPayTxHistoryItemsConverter.kt +++ b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/model/transformers/TangemPayTxHistoryItemsConverter.kt @@ -37,10 +37,10 @@ internal class TangemPayTxHistoryItemsConverter( val localDate = spend.date.withZone(DateTimeZone.getDefault()) val amountPrefix = when { spend.amount.isZero() -> "" - spend.status == TangemPayTxHistoryItem.Status.DECLINED -> "" - else -> StringsSigns.MINUS + spend.status == TangemPayTxHistoryItem.Status.DECLINED || spend.amount.isPositive() -> StringsSigns.MINUS + else -> StringsSigns.PLUS } - val amount = amountPrefix + spend.amount.format { + val amount = amountPrefix + spend.amount.abs().format { fiat(fiatCurrencyCode = spend.currency.currencyCode, fiatCurrencySymbol = spend.currency.symbol) } return TangemPayTransactionState.Content.Spend( @@ -48,8 +48,9 @@ internal class TangemPayTxHistoryItemsConverter( onClick = { txHistoryUiActions.onTransactionClick(spend) }, amount = amount, amountColor = themedColor { - when (spend.status) { - TangemPayTxHistoryItem.Status.DECLINED -> TangemTheme.colors.text.warning + when { + spend.status == TangemPayTxHistoryItem.Status.DECLINED -> TangemTheme.colors.text.warning + amountPrefix == StringsSigns.PLUS -> TangemTheme.colors.text.accent else -> TangemTheme.colors.text.primary1 } }, diff --git a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/ui/TangempayTxDetailsUi.kt b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/ui/TangempayTxDetailsUi.kt index c406c83696..111edfb273 100644 --- a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/ui/TangempayTxDetailsUi.kt +++ b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/ui/TangempayTxDetailsUi.kt @@ -26,6 +26,7 @@ import com.tangem.core.ui.components.label.entity.LabelUM import com.tangem.core.ui.components.notifications.Notification import com.tangem.core.ui.components.notifications.NotificationConfig import com.tangem.core.ui.extensions.* +import com.tangem.core.ui.res.TangemColorPalette import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemThemePreview import com.tangem.features.tangempay.details.impl.R @@ -83,21 +84,24 @@ internal fun TangemPayTxHistoryDetailsContent(state: TangemPayTxHistoryDetailsUM style = TangemTheme.typography.head, color = state.transactionAmountColor.resolveReference(), ) - state.localTransactionText?.let { localTransaction -> + if (state.localTransactionText != null) { Text( modifier = Modifier.padding(top = 4.dp), - text = localTransaction.orMaskWithStars(state.isBalanceHidden), + text = state.localTransactionText.orMaskWithStars(state.isBalanceHidden), style = TangemTheme.typography.body2, color = TangemTheme.colors.text.tertiary, ) } - state.labelState?.let { Label(state = state.labelState, modifier = Modifier.padding(top = 12.dp)) } + if (state.labelState != null) { + Label(state = state.labelState, modifier = Modifier.padding(top = 12.dp)) + } SpacerH32() - state.notification?.let { + if (state.notification != null) { Notification( - config = state.notification, - titleColor = TangemTheme.colors.text.tertiary, - iconTint = TangemTheme.colors.icon.secondary, + config = state.notification.config, + titleColor = state.notification.titleColor.resolveReference(), + iconTint = state.notification.iconTint.resolveReference(), + containerColor = state.notification.containerColor?.resolveReference(), ) } ButtonsContainer( @@ -195,10 +199,15 @@ private class TangemPayTxHistoryDetailsUMProvider : CollectionPreviewParameterPr text = resourceReference(R.string.tangem_pay_status_declined), style = LabelStyle.WARNING, ), - notification = NotificationConfig( - title = stringReference("The bank rejected this transaction request."), - subtitle = TextReference.EMPTY, - iconResId = R.drawable.ic_token_info_24, + notification = TangemPayTxHistoryDetailsUM.NotificationState( + config = NotificationConfig( + title = stringReference("The bank rejected this transaction request."), + subtitle = TextReference.EMPTY, + iconResId = R.drawable.ic_token_info_24, + ), + titleColor = themedColor { TangemTheme.colors.text.warning }, + iconTint = themedColor { TangemTheme.colors.icon.warning }, + containerColor = themedColor { TangemColorPalette.Amaranth.copy(alpha = 0.1F) }, ), buttons = persistentListOf( TangemPayTxHistoryDetailsUM.ButtonState( @@ -240,10 +249,15 @@ private class TangemPayTxHistoryDetailsUMProvider : CollectionPreviewParameterPr transactionAmountColor = themedColor { TangemTheme.colors.text.primary1 }, localTransactionText = null, labelState = null, - notification = NotificationConfig( - title = stringReference("This fee goes to cover the cost of handling your transfer."), - subtitle = TextReference.EMPTY, - iconResId = R.drawable.ic_token_info_24, + notification = TangemPayTxHistoryDetailsUM.NotificationState( + config = NotificationConfig( + title = stringReference("This fee goes to cover the cost of handling your transfer."), + subtitle = TextReference.EMPTY, + iconResId = R.drawable.ic_token_info_24, + ), + titleColor = themedColor { TangemTheme.colors.text.tertiary }, + iconTint = themedColor { TangemTheme.colors.icon.secondary }, + containerColor = null, ), buttons = persistentListOf( TangemPayTxHistoryDetailsUM.ButtonState( 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 c68d1fa5e0..5790de110c 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 @@ -65,6 +65,7 @@ import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemThemePreview import com.tangem.core.ui.test.MainScreenTestTags import com.tangem.core.ui.test.MarketTooltipTestTags +import com.tangem.core.ui.utils.TangemSharedTransitionLayout import com.tangem.core.ui.utils.lineTo import com.tangem.core.ui.utils.moveTo import com.tangem.core.ui.utils.toPx @@ -180,79 +181,81 @@ private fun WalletContent( ) } ?: PaddingValues(bottom = TangemTheme.dimens.spacing92 + bottomBarHeight) - LazyColumn( - modifier = Modifier.testTag(MainScreenTestTags.SCREEN_CONTAINER), - state = listState, - contentPadding = contentPadding, - horizontalAlignment = Alignment.CenterHorizontally, - ) { - 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 }, + TangemSharedTransitionLayout { + LazyColumn( + modifier = Modifier.testTag(MainScreenTestTags.SCREEN_CONTAINER), + state = listState, + contentPadding = contentPadding, + horizontalAlignment = Alignment.CenterHorizontally, ) { - WalletsList( - modifier = Modifier.animateItem(fadeInSpec = null, fadeOutSpec = null), - lazyListState = walletsListState, - wallets = state.wallets.map(WalletState::walletCardState).toImmutableList(), - isBalanceHidden = state.isHidingMode, - ) - } - - when (selectedWallet) { - is WalletState.MultiCurrency -> { - actions( - actions = selectedWallet.buttons, - selectedWalletIndex = selectedWalletIndex, - modifier = movableItemModifier.padding(top = betweenItemsPadding), - ) - } - is WalletState.SingleCurrency -> { - lazyActions( - actions = selectedWallet.buttons, - selectedWalletIndex = selectedWalletIndex, - modifier = movableItemModifier.padding(top = betweenItemsPadding), - ) - } - } - - notifications(configs = selectedWallet.warnings, modifier = itemModifier) - - if (selectedWallet is WalletState.MultiCurrency) { item( - key = "TangemPayMainScreenBlock", - contentType = selectedWallet.tangemPayState::class.java, + // !!! 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 }, ) { - TangemPayMainScreenBlock( - state = selectedWallet.tangemPayState, + WalletsList( + modifier = Modifier.animateItem(fadeInSpec = null, fadeOutSpec = null), + lazyListState = walletsListState, + wallets = state.wallets.map(WalletState::walletCardState).toImmutableList(), isBalanceHidden = state.isHidingMode, - modifier = itemModifier, ) } - } - (selectedWallet as? WalletState.SingleCurrency)?.let { walletState -> - walletState.marketPriceBlockState?.let { marketPriceBlockState -> - marketPriceBlock(state = marketPriceBlockState, modifier = itemModifier) + when (selectedWallet) { + is WalletState.MultiCurrency -> { + actions( + actions = selectedWallet.buttons, + selectedWalletIndex = selectedWalletIndex, + modifier = movableItemModifier.padding(top = betweenItemsPadding), + ) + } + is WalletState.SingleCurrency -> { + lazyActions( + actions = selectedWallet.buttons, + selectedWalletIndex = selectedWalletIndex, + modifier = movableItemModifier.padding(top = betweenItemsPadding), + ) + } } - if (walletState is WalletState.SingleCurrency.Content) { - expressTransactionsItems( - expressTxs = walletState.expressTxsToDisplay, - modifier = itemModifier, - ) + + notifications(configs = selectedWallet.warnings, modifier = itemModifier) + + if (selectedWallet is WalletState.MultiCurrency) { + item( + key = "TangemPayMainScreenBlock", + contentType = selectedWallet.tangemPayState::class.java, + ) { + TangemPayMainScreenBlock( + state = selectedWallet.tangemPayState, + isBalanceHidden = state.isHidingMode, + modifier = itemModifier, + ) + } } + + (selectedWallet as? WalletState.SingleCurrency)?.let { walletState -> + walletState.marketPriceBlockState?.let { marketPriceBlockState -> + marketPriceBlock(state = marketPriceBlockState, modifier = itemModifier) + } + if (walletState is WalletState.SingleCurrency.Content) { + expressTransactionsItems( + expressTxs = walletState.expressTxsToDisplay, + modifier = itemModifier, + ) + } + } + + contentItems( + state = selectedWallet, + txHistoryItems = txHistoryItems, + isBalanceHidden = state.isHidingMode, + modifier = movableItemModifier, + ) + + nftCollections(state = selectedWallet, itemModifier = itemModifier) + + organizeTokens(state = selectedWallet, itemModifier = itemModifier) } - - contentItems( - state = selectedWallet, - txHistoryItems = txHistoryItems, - isBalanceHidden = state.isHidingMode, - modifier = movableItemModifier, - ) - - nftCollections(state = selectedWallet, itemModifier = itemModifier) - - organizeTokens(state = selectedWallet, itemModifier = itemModifier) } ShowBottomSheet(bottomSheetConfig = selectedWallet.bottomSheetConfig) diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/multicurrency/MultiCurrencyAccountContent.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/multicurrency/MultiCurrencyAccountContent.kt index 0cf245d560..66c94df727 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/multicurrency/MultiCurrencyAccountContent.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/multicurrency/MultiCurrencyAccountContent.kt @@ -84,7 +84,7 @@ internal fun LazyListScope.portfolioTokensList( } itemsIndexed( items = tokens, - key = { _, item -> item.id }, + key = { _, item -> item.id.toString() + "-portfolio-${portfolio.id}" }, contentType = { _, item -> item::class.java }, itemContent = { tokenIndex, token -> val indexWithHeader = tokenIndex.inc() diff --git a/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/main/model/YieldSupplyModel.kt b/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/main/model/YieldSupplyModel.kt index 6aef4d48d4..381a2c9fab 100644 --- a/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/main/model/YieldSupplyModel.kt +++ b/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/main/model/YieldSupplyModel.kt @@ -31,8 +31,6 @@ import com.tangem.features.yield.supply.impl.R import com.tangem.features.yield.supply.impl.main.entity.YieldSupplyUM import com.tangem.features.yield.supply.impl.main.model.transformers.YieldSupplyTokenStatusSuccessTransformer import com.tangem.utils.coroutines.CoroutineDispatcherProvider -import com.tangem.utils.coroutines.JobHolder -import com.tangem.utils.coroutines.saveIn import com.tangem.utils.transformer.update import kotlinx.coroutines.flow.* import kotlinx.coroutines.launch @@ -57,6 +55,7 @@ internal class YieldSupplyModel @Inject constructor( private val yieldSupplyActivateUseCase: YieldSupplyActivateUseCase, private val yieldSupplyDeactivateUseCase: YieldSupplyDeactivateUseCase, private val yieldSupplyEnterStatusUseCase: YieldSupplyEnterStatusUseCase, + private val yieldSupplyEnterStatusFlowUseCase: YieldSupplyEnterStatusFlowUseCase, private val yieldSupplyMinAmountUseCase: YieldSupplyMinAmountUseCase, private val yieldSupplyGetDustMinAmountUseCase: YieldSupplyGetDustMinAmountUseCase, ) : Model(), YieldSupplyClickIntents { @@ -71,8 +70,6 @@ internal class YieldSupplyModel @Inject constructor( var userWallet: UserWallet by Delegates.notNull() private var latestCryptoCurrencyStatus: CryptoCurrencyStatus? = null - private val loadStatusJobHolder = JobHolder() - private val isFirstCryptoCurrencyStatusEmission = AtomicBoolean(true) init { @@ -84,7 +81,7 @@ internal class YieldSupplyModel @Inject constructor( appCurrency = getSelectedAppCurrencyUseCase.invokeSync().getOrElse { AppCurrency.Default } val isAvailable = yieldSupplyIsAvailableUseCase(params.userWalletId, params.cryptoCurrency) if (isAvailable) { - subscribeOnCurrencyStatusUpdates() + loadUserWalletData() singleNetworkStatusFetcher( params = SingleNetworkStatusFetcher.Params( userWalletId = params.userWalletId, @@ -95,30 +92,12 @@ internal class YieldSupplyModel @Inject constructor( } } - private fun subscribeOnCurrencyStatusUpdates() { + private fun loadUserWalletData() { modelScope.launch { getUserWalletUseCase(params.userWalletId).fold( ifRight = { wallet -> userWallet = wallet - - getSingleCryptoCurrencyStatusUseCase.invokeMultiWallet( - userWalletId = params.userWalletId, - currencyId = cryptoCurrency.id, - isSingleWalletWithTokens = false, - ).onEach { maybeCryptoCurrency -> - maybeCryptoCurrency.fold( - ifRight = { cryptoCurrencyStatus -> - latestCryptoCurrencyStatus = cryptoCurrencyStatus - if (isFirstCryptoCurrencyStatusEmission.compareAndSet(true, false)) { - sendInfoAboutProtocolStatus(cryptoCurrencyStatus) - } - onCryptoCurrencyStatusUpdated(cryptoCurrencyStatus) - }, - ifLeft = { - Timber.w(it.toString()) - }, - ) - }.launchIn(modelScope) + subscribeOnCurrencyStatusUpdates() }, ifLeft = { Timber.w(it.toString()) @@ -128,6 +107,36 @@ internal class YieldSupplyModel @Inject constructor( } } + private fun subscribeOnCurrencyStatusUpdates() { + combine( + getSingleCryptoCurrencyStatusUseCase.invokeMultiWallet( + userWalletId = params.userWalletId, + currencyId = cryptoCurrency.id, + isSingleWalletWithTokens = false, + ), + yieldSupplyEnterStatusFlowUseCase( + userWalletId = params.userWalletId, + cryptoCurrency = cryptoCurrency, + ), + ) { maybeCryptoCurrency, _ -> + maybeCryptoCurrency + }.flowOn(dispatchers.io) + .onEach { maybeCryptoCurrency -> + maybeCryptoCurrency.fold( + ifRight = { cryptoCurrencyStatus -> + latestCryptoCurrencyStatus = cryptoCurrencyStatus + if (isFirstCryptoCurrencyStatusEmission.compareAndSet(true, false)) { + sendInfoAboutProtocolStatus(cryptoCurrencyStatus) + } + onCryptoCurrencyStatusUpdated(cryptoCurrencyStatus) + }, + ifLeft = { + Timber.w(it.toString()) + }, + ) + }.launchIn(modelScope) + } + private suspend fun loadTokenStatus() { val cryptoCurrencyToken = cryptoCurrency as? CryptoCurrency.Token ?: return yieldSupplyGetTokenStatusUseCase(cryptoCurrencyToken) @@ -168,13 +177,11 @@ internal class YieldSupplyModel @Inject constructor( ) } - private fun onCryptoCurrencyStatusUpdated(cryptoCurrencyStatus: CryptoCurrencyStatus) = modelScope.launch( - dispatchers.default, - ) { + private suspend fun onCryptoCurrencyStatusUpdated(cryptoCurrencyStatus: CryptoCurrencyStatus) { val isCryptoCurrencyStatusFromCache = cryptoCurrencyStatus.value.sources.networkSource != StatusSource.ACTUAL val processing = uiState.value is YieldSupplyUM.Processing if (isCryptoCurrencyStatusFromCache && processing) { - return@launch + return } val pendingStatus = yieldSupplyEnterStatusUseCase( @@ -187,7 +194,7 @@ internal class YieldSupplyModel @Inject constructor( } else { loadStatus(cryptoCurrencyStatus) } - }.saveIn(loadStatusJobHolder) + } private fun showProcessing(status: YieldSupplyPendingStatus) { uiState.update { diff --git a/tangem-android-tools b/tangem-android-tools index cfc2c0ab47..ab6aee1d49 160000 --- a/tangem-android-tools +++ b/tangem-android-tools @@ -1 +1 @@ -Subproject commit cfc2c0ab47e2e821f38f463f32f120d620804c72 +Subproject commit ab6aee1d49ac0cfca644330a525dced725d5a257