From 0ce75f144563a26b1a6634636d890eef48091379 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 19 Mar 2026 14:58:45 +0500 Subject: [PATCH] Updated on 2026-08-14 --- .../tangem/tap/routing/utils/ChildFactory.kt | 1 + .../com/tangem/common/routing/AppRoute.kt | 1 + .../local/preferences/PreferencesKeys.kt | 2 + core/res/src/main/res/values/strings.xml | 3 ++ .../tokens/model/tokensync/DiscoveredToken.kt | 14 +++++ .../model/tokensync/TokenSyncProgress.kt | 22 ++++++++ .../tokens/repository/TokenSyncRepository.kt | 23 ++++++++ .../component/ManageTokensSource.kt | 1 + .../utils/WalletWarningsAnalyticsSender.kt | 1 + .../state/model/WalletAdditionalInfo.kt | 1 + .../wallet/state/model/WalletNotification.kt | 17 ++++++ .../SetTokenSyncProgressTransformer.kt | 52 +++++++++++++++++++ .../wallet/ui/components/common/WalletCard.kt | 18 ++++++- 13 files changed, 154 insertions(+), 2 deletions(-) create mode 100644 domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/tokensync/DiscoveredToken.kt create mode 100644 domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/tokensync/TokenSyncProgress.kt create mode 100644 domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/TokenSyncRepository.kt create mode 100644 features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/SetTokenSyncProgressTransformer.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 6078133aea..6817e44fe9 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 @@ -140,6 +140,7 @@ internal class ChildFactory @Inject constructor( AppRoute.ManageTokens.Source.SETTINGS -> ManageTokensSource.SETTINGS AppRoute.ManageTokens.Source.STORIES -> ManageTokensSource.STORIES AppRoute.ManageTokens.Source.ACCOUNT -> ManageTokensSource.ACCOUNT + AppRoute.ManageTokens.Source.TOKEN_SYNC_BANNER -> ManageTokensSource.TOKEN_SYNC_BANNER } val mode = route.accountId?.let { ManageTokensMode.Account(it) } ?: ManageTokensMode.None 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 8b1bc37667..bf5e754ca3 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 @@ -139,6 +139,7 @@ sealed class AppRoute(val path: String) : Route { STORIES, SETTINGS, ACCOUNT, + TOKEN_SYNC_BANNER, } } diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/preferences/PreferencesKeys.kt b/core/datasource/src/main/java/com/tangem/datasource/local/preferences/PreferencesKeys.kt index c70f061ee1..fbaaff2c62 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/local/preferences/PreferencesKeys.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/local/preferences/PreferencesKeys.kt @@ -140,6 +140,8 @@ object PreferencesKeys { val HAS_HAD_FIRST_TOP_UP_KEY by lazy { stringPreferencesKey(name = "hasHadFirstTopUp") } + val PENDING_DISCOVERY_SYNC_KEY by lazy { stringPreferencesKey(name = "pendingDiscoverySync") } + // region Notifications val NOTIFICATIONS_APPLICATION_ID_KEY by lazy { stringPreferencesKey(name = "notificationsApplicationId") } diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml index 020150535e..9dd3c09886 100644 --- a/core/res/src/main/res/values/strings.xml +++ b/core/res/src/main/res/values/strings.xml @@ -692,6 +692,9 @@ Tap to scan Tap to sign Tap the card or ring + Your wallet is synced and ready.\nSome tokens missing? + Wallet successfully imported + Restoring %d% You have updated biometrics, scan your card or ring to enter Your balance should be higher than the fee value to make a transfer Not enough balance diff --git a/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/tokensync/DiscoveredToken.kt b/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/tokensync/DiscoveredToken.kt new file mode 100644 index 0000000000..ddf1a0069b --- /dev/null +++ b/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/tokensync/DiscoveredToken.kt @@ -0,0 +1,14 @@ +package com.tangem.domain.tokens.model.tokensync + +import java.math.BigDecimal + +data class DiscoveredToken( + val contractAddress: String?, + val symbol: String, + val name: String, + val decimals: Int, + val amount: BigDecimal, + val isNativeToken: Boolean, + val currencyId: String?, + val networkId: String, +) \ No newline at end of file diff --git a/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/tokensync/TokenSyncProgress.kt b/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/tokensync/TokenSyncProgress.kt new file mode 100644 index 0000000000..9e94d71268 --- /dev/null +++ b/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/tokensync/TokenSyncProgress.kt @@ -0,0 +1,22 @@ +package com.tangem.domain.tokens.model.tokensync + +sealed class TokenSyncProgress { + + data object Idle : TokenSyncProgress() + + data class InProgress( + val completedNetworks: Int, + val totalNetworks: Int, + ) : TokenSyncProgress() { + val progressPercent: Int + get() = if (totalNetworks > 0) { + completedNetworks * 100 / totalNetworks + } else { + 0 + } + } + + data object Completed : TokenSyncProgress() + + data class Error(val cause: Throwable) : TokenSyncProgress() +} \ No newline at end of file diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/TokenSyncRepository.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/TokenSyncRepository.kt new file mode 100644 index 0000000000..5919dd5414 --- /dev/null +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/TokenSyncRepository.kt @@ -0,0 +1,23 @@ +package com.tangem.domain.tokens.repository + +import com.tangem.domain.models.currency.CryptoCurrency +import com.tangem.domain.models.wallet.UserWalletId +import com.tangem.domain.tokens.model.tokensync.TokenSyncProgress +import kotlinx.coroutines.flow.Flow + +interface TokenSyncRepository { + + suspend fun runSync(userWalletId: UserWalletId) + + suspend fun getPendingSyncWalletIds(): List + + fun observeSyncProgress(userWalletId: UserWalletId): Flow + + fun acknowledgeCompletion(userWalletId: UserWalletId) + + suspend fun clearPendingFlag(userWalletId: UserWalletId) + + suspend fun getDiscoveredCurrencies(userWalletId: UserWalletId): List + + suspend fun clearDiscoveredTokens(userWalletId: UserWalletId) +} \ No newline at end of file diff --git a/features/manage-tokens/api/src/main/kotlin/com/tangem/features/managetokens/component/ManageTokensSource.kt b/features/manage-tokens/api/src/main/kotlin/com/tangem/features/managetokens/component/ManageTokensSource.kt index fcce6daea7..38034501c5 100644 --- a/features/manage-tokens/api/src/main/kotlin/com/tangem/features/managetokens/component/ManageTokensSource.kt +++ b/features/manage-tokens/api/src/main/kotlin/com/tangem/features/managetokens/component/ManageTokensSource.kt @@ -9,6 +9,7 @@ enum class ManageTokensSource(val analyticsName: String) { ONBOARDING(analyticsName = "Onboarding"), SETTINGS(analyticsName = "Wallet Settings"), ACCOUNT(analyticsName = "Account"), + TOKEN_SYNC_BANNER(analyticsName = "Token Sync Banner"), SEND_VIA_SWAP(analyticsName = "SendViaSwap"), } diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/analytics/utils/WalletWarningsAnalyticsSender.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/analytics/utils/WalletWarningsAnalyticsSender.kt index 41fe676459..63e329b73f 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/analytics/utils/WalletWarningsAnalyticsSender.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/analytics/utils/WalletWarningsAnalyticsSender.kt @@ -122,6 +122,7 @@ internal class WalletWarningsAnalyticsSender @Inject constructor( is WalletNotification.Warning.TangemPayRefreshNeeded -> null is WalletNotification.Warning.TangemPayUnreachable -> null is WalletNotification.UpgradeHotWalletPromo -> null + is WalletNotification.TokenSyncCompleted -> null } } diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletAdditionalInfo.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletAdditionalInfo.kt index a12cd21993..c18257cf76 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletAdditionalInfo.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletAdditionalInfo.kt @@ -8,4 +8,5 @@ data class WalletAdditionalInfo( val hideable: Boolean, val content: TextReference, val isHotBackedUp: Boolean = false, + val shouldShowProgress: Boolean = false, ) \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletNotification.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletNotification.kt index ed1601ae6a..e3110857f1 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletNotification.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletNotification.kt @@ -463,4 +463,21 @@ sealed class WalletNotification(val config: NotificationConfig) { iconSize = 72.dp, ), ) + + data class TokenSyncCompleted( + val onCloseClick: () -> Unit, + val onManageTokensClick: () -> Unit, + ) : WalletNotification( + config = NotificationConfig( + title = resourceReference(R.string.initial_wallet_sync_banner_title), + subtitle = resourceReference(R.string.initial_wallet_sync_banner_description), + iconResId = R.drawable.ic_check_circle_24, + iconTint = IconTint.Accent, + onCloseClick = onCloseClick, + buttonsState = ButtonsState.SecondaryButtonConfig( + text = resourceReference(R.string.main_manage_tokens), + onClick = onManageTokensClick, + ), + ), + ) } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/SetTokenSyncProgressTransformer.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/SetTokenSyncProgressTransformer.kt new file mode 100644 index 0000000000..283537ff7a --- /dev/null +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/SetTokenSyncProgressTransformer.kt @@ -0,0 +1,52 @@ +package com.tangem.feature.wallet.presentation.wallet.state.transformers + +import com.tangem.core.ui.extensions.resourceReference +import com.tangem.core.ui.extensions.wrappedList +import com.tangem.domain.models.wallet.UserWalletId +import com.tangem.feature.wallet.impl.R +import com.tangem.feature.wallet.presentation.wallet.state.model.WalletAdditionalInfo +import com.tangem.feature.wallet.presentation.wallet.state.model.WalletCardState +import com.tangem.feature.wallet.presentation.wallet.state.model.WalletState +import com.tangem.feature.wallet.presentation.wallet.state.model.WalletUM + +internal class SetTokenSyncProgressTransformer( + userWalletId: UserWalletId, + private val progressPercent: Int, +) : WalletStateTransformer(userWalletId) { + + override fun transform(prevState: WalletState): WalletState { + return when (prevState) { + is WalletState.MultiCurrency.Content -> { + val updatedCardState = updateCardState(prevState.walletCardState) + prevState.copy(walletCardState = updatedCardState) + } + else -> { + prevState + } + } + } + + override fun transform(walletUM: WalletUM): WalletUM { + return walletUM + } + + private fun updateCardState(cardState: WalletCardState): WalletCardState { + val additionalInfo = WalletAdditionalInfo( + hideable = false, + content = resourceReference( + id = R.string.initial_wallet_sync_restore_progress, + formatArgs = wrappedList(progressPercent), + ), + shouldShowProgress = true, + ) + return when (cardState) { + is WalletCardState.Loading -> { + cardState.copy(additionalInfo = additionalInfo) + } + is WalletCardState.Content -> { + cardState.copy(additionalInfo = additionalInfo) + } + else -> cardState + } + } +} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/common/WalletCard.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/common/WalletCard.kt index 651707a1c3..26547001a4 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/common/WalletCard.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/common/WalletCard.kt @@ -12,6 +12,7 @@ import androidx.compose.foundation.indication import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.interaction.PressInteraction import androidx.compose.foundation.layout.* +import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.text.TextAutoSize import androidx.compose.material3.* @@ -164,6 +165,7 @@ private fun CardContainer(state: WalletCardState, isBalanceHidden: Boolean, item } AdditionalInfo( text = additionalText, + showProgress = state.additionalInfo?.shouldShowProgress == true, modifier = Modifier.conditional( state.imageResId == null, ) { fillMaxWidth() }, @@ -298,7 +300,7 @@ private fun Modifier.nonContentBalanceSize(dimens: TangemDimens): Modifier { } @Composable -private fun AdditionalInfo(text: TextReference?, modifier: Modifier = Modifier) { +private fun AdditionalInfo(text: TextReference?, showProgress: Boolean, modifier: Modifier = Modifier) { AnimatedContent( targetState = text, label = "Update the additional text", @@ -309,7 +311,19 @@ private fun AdditionalInfo(text: TextReference?, modifier: Modifier = Modifier) }, ) { animatedText -> if (animatedText != null) { - AdditionalInfoText(text = animatedText) + Row( + horizontalArrangement = Arrangement.spacedBy(6.dp), + ) { + AdditionalInfoText(text = animatedText) + if (showProgress) { + CircularProgressIndicator( + modifier = Modifier + .size(TangemTheme.dimens.size16), + color = TangemTheme.colors.icon.accent, + strokeWidth = TangemTheme.dimens.size2, + ) + } + } } else { RectangleShimmer(modifier = Modifier.nonContentAdditionalInfoSize(dimens = TangemTheme.dimens)) }