From 1c7148015fd0232388076a40ca21d8d7d0ffcc46 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 25 Mar 2024 13:56:11 +0500 Subject: [PATCH 01/67] Updated on 2026-08-14 --- .../main/java/com/tangem/tap/MainActivity.kt | 31 ++++ .../java/com/tangem/tap/di/ActivityModule.kt | 19 ++ .../tap/di/domain/TokensDomainModule.kt | 9 + .../local/preferences/PreferencesKeys.kt | 10 ++ .../tangem/data/tokens/di/TokensDataModule.kt | 12 ++ .../repository/DefaultNetworksRepository.kt | 1 + ...ultPolkadotAccountHealthCheckRepository.kt | 169 ++++++++++++++++++ .../GetPolkadotCheckHasImmortalUseCase.kt | 10 ++ .../tokens/GetPolkadotCheckHasResetUseCase.kt | 11 ++ .../RunPolkadotAccountHealthCheckUseCase.kt | 17 ++ .../PolkadotAccountHealthCheckRepository.kt | 14 ++ .../analytics/WalletScreenAnalyticsEvent.kt | 62 ++++--- .../implementors/MultiWalletContentLoader.kt | 3 + .../MultiWalletContentLoaderFactory.kt | 3 + .../SingleWalletWithTokenContentLoader.kt | 3 + ...ngleWalletWithTokenContentLoaderFactory.kt | 3 + .../subscribers/BasicTokenListSubscriber.kt | 20 +++ .../MultiWalletTokenListSubscriber.kt | 3 + .../SingleWalletWithTokenListSubscriber.kt | 3 + gradle/dependencies.toml | 2 +- 20 files changed, 383 insertions(+), 22 deletions(-) create mode 100644 data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultPolkadotAccountHealthCheckRepository.kt create mode 100644 domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetPolkadotCheckHasImmortalUseCase.kt create mode 100644 domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetPolkadotCheckHasResetUseCase.kt create mode 100644 domain/tokens/src/main/kotlin/com/tangem/domain/tokens/RunPolkadotAccountHealthCheckUseCase.kt create mode 100644 domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/PolkadotAccountHealthCheckRepository.kt diff --git a/app/src/main/java/com/tangem/tap/MainActivity.kt b/app/src/main/java/com/tangem/tap/MainActivity.kt index 116e6dcf12..3a06ab01ef 100644 --- a/app/src/main/java/com/tangem/tap/MainActivity.kt +++ b/app/src/main/java/com/tangem/tap/MainActivity.kt @@ -19,12 +19,14 @@ import androidx.core.content.ContextCompat import androidx.core.os.bundleOf import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen import androidx.core.view.WindowCompat +import androidx.lifecycle.Lifecycle import androidx.lifecycle.flowWithLifecycle import androidx.lifecycle.lifecycleScope import arrow.core.getOrElse import by.kirich1409.viewbindingdelegate.viewBinding import com.google.android.material.snackbar.BaseTransientBottomBar import com.google.android.material.snackbar.Snackbar +import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.core.deeplink.DeepLinksRegistry import com.tangem.core.navigation.AppScreen import com.tangem.core.navigation.NavigationAction @@ -35,10 +37,13 @@ import com.tangem.data.card.sdk.CardSdkLifecycleObserver import com.tangem.domain.apptheme.model.AppThemeMode import com.tangem.domain.card.ScanCardUseCase import com.tangem.domain.card.repository.CardSdkConfigRepository +import com.tangem.domain.tokens.GetPolkadotCheckHasImmortalUseCase +import com.tangem.domain.tokens.GetPolkadotCheckHasResetUseCase import com.tangem.domain.wallets.legacy.UserWalletsListManager import com.tangem.domain.wallets.legacy.UserWalletsListManagerFeatureToggles import com.tangem.domain.wallets.legacy.asLockable import com.tangem.feature.qrscanning.QrScanningRouter +import com.tangem.feature.wallet.presentation.wallet.analytics.WalletScreenAnalyticsEvent import com.tangem.features.managetokens.navigation.ManageTokensUi import com.tangem.features.send.api.navigation.SendRouter import com.tangem.features.tester.api.TesterRouter @@ -99,6 +104,7 @@ val userWalletsListManagerSafe: UserWalletsListManager? val userWalletsListManager: UserWalletsListManager get() = userWalletsListManagerSafe!! +@Suppress("LargeClass") @AndroidEntryPoint class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbackHolder { @@ -148,6 +154,15 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac @Inject lateinit var generalUserWalletsListManager: UserWalletsListManager + @Inject + lateinit var getPolkadotCheckHasResetUseCase: GetPolkadotCheckHasResetUseCase + + @Inject + lateinit var getPolkadotCheckHasImmortalUseCase: GetPolkadotCheckHasImmortalUseCase + + @Inject + lateinit var analyticsEventsHandler: AnalyticsEventHandler + internal val viewModel: MainViewModel by viewModels() private lateinit var appThemeModeFlow: SharedFlow @@ -177,6 +192,7 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac checkForNotificationPermission() observeStateUpdates() + observePolkadotAccountHealthCheck() if (intent != null) { deepLinksRegistry.launch(intent) @@ -482,4 +498,19 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.POST_NOTIFICATIONS), 0) } } + + private fun observePolkadotAccountHealthCheck() { + lifecycleScope.launch { + getPolkadotCheckHasResetUseCase() + .flowWithLifecycle(lifecycle, minActiveState = Lifecycle.State.CREATED) + .collect { + analyticsEventsHandler.send(WalletScreenAnalyticsEvent.Token.PolkadotAccountReset(it)) + } + getPolkadotCheckHasImmortalUseCase() + .flowWithLifecycle(lifecycle, minActiveState = Lifecycle.State.CREATED) + .collect { + analyticsEventsHandler.send(WalletScreenAnalyticsEvent.Token.PolkadotImmortalTransactions(it)) + } + } + } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/di/ActivityModule.kt b/app/src/main/java/com/tangem/tap/di/ActivityModule.kt index 2ff65f28a2..9a0f838f4a 100644 --- a/app/src/main/java/com/tangem/tap/di/ActivityModule.kt +++ b/app/src/main/java/com/tangem/tap/di/ActivityModule.kt @@ -4,6 +4,9 @@ import android.content.Context import com.tangem.domain.card.ScanCardUseCase import com.tangem.domain.card.repository.CardSdkConfigRepository import com.tangem.domain.exchange.RampStateManager +import com.tangem.domain.tokens.GetPolkadotCheckHasImmortalUseCase +import com.tangem.domain.tokens.GetPolkadotCheckHasResetUseCase +import com.tangem.domain.tokens.repository.PolkadotAccountHealthCheckRepository import com.tangem.tap.domain.TangemSdkManager import com.tangem.tap.domain.scanCard.repository.DefaultScanCardRepository import com.tangem.tap.network.exchangeServices.DefaultRampManager @@ -57,4 +60,20 @@ internal object ActivityModule { fun provideActivityDelayedWorkCoroutineScope(): CoroutineScope { return CoroutineScope(SupervisorJob() + Dispatchers.IO) } + + @Provides + @Singleton + fun provideGetPolkadotCheckHasResetUseCase( + polkadotAccountHealthCheckRepository: PolkadotAccountHealthCheckRepository, + ): GetPolkadotCheckHasResetUseCase { + return GetPolkadotCheckHasResetUseCase(polkadotAccountHealthCheckRepository) + } + + @Provides + @Singleton + fun provideGetPolkadotCheckHasImmortalUseCase( + polkadotAccountHealthCheckRepository: PolkadotAccountHealthCheckRepository, + ): GetPolkadotCheckHasImmortalUseCase { + return GetPolkadotCheckHasImmortalUseCase(polkadotAccountHealthCheckRepository) + } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/di/domain/TokensDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/TokensDomainModule.kt index c67960a860..8f377b67ad 100644 --- a/app/src/main/java/com/tangem/tap/di/domain/TokensDomainModule.kt +++ b/app/src/main/java/com/tangem/tap/di/domain/TokensDomainModule.kt @@ -332,4 +332,13 @@ internal object TokensDomainModule { ): IsAmountSubtractAvailableUseCase { return IsAmountSubtractAvailableUseCase(currenciesRepository, dispatchers) } + + @Provides + @ViewModelScoped + fun provideRunPolkadotAccountHealthCheckUseCase( + repository: PolkadotAccountHealthCheckRepository, + dispatchers: CoroutineDispatcherProvider, + ): RunPolkadotAccountHealthCheckUseCase { + return RunPolkadotAccountHealthCheckUseCase(repository, dispatchers) + } } \ No newline at end of file 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 22d8aa8ea3..464387831e 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 @@ -63,6 +63,16 @@ object PreferencesKeys { val APP_LOGS_KEY by lazy { stringPreferencesKey(name = "app_logs") } + val POLKADOT_HEALTH_CHECK_LAST_INDEXED_TX by lazy { + stringPreferencesKey(name = "POLKADOT_HEALTH_CHECK_LAST_INDEXED_TX") + } + val POLKADOT_HEALTH_CHECKED_RESET_ACCOUNTS by lazy { + stringPreferencesKey(name = "POLKADOT_HEALTH_CHECKED_RESET_ACCOUNTS") + } + val POLKADOT_HEALTH_CHECKED_IMMUTABLE_ACCOUNTS by lazy { + stringPreferencesKey(name = "POLKADOT_HEALTH_CHECKED_IMMUTABLE_ACCOUNTS") + } + fun getStart2CoinTOSAcceptedKey(region: String?) = booleanPreferencesKey(name = "start2Coin_tos_accepted_$region") } diff --git a/data/tokens/src/main/kotlin/com/tangem/data/tokens/di/TokensDataModule.kt b/data/tokens/src/main/kotlin/com/tangem/data/tokens/di/TokensDataModule.kt index 05c56cfaf9..ef0aef1c7e 100644 --- a/data/tokens/src/main/kotlin/com/tangem/data/tokens/di/TokensDataModule.kt +++ b/data/tokens/src/main/kotlin/com/tangem/data/tokens/di/TokensDataModule.kt @@ -119,4 +119,16 @@ internal object TokensDataModule { fun provideCurrencyChecksRepository(walletManagersFacade: WalletManagersFacade): CurrencyChecksRepository { return DefaultCurrencyChecksRepository(walletManagersFacade = walletManagersFacade) } + + @Provides + @Singleton + fun providePolkadotAccountHealthCheckRepository( + walletManagersFacade: WalletManagersFacade, + appPreferencesStore: AppPreferencesStore, + ): PolkadotAccountHealthCheckRepository { + return DefaultPolkadotAccountHealthCheckRepository( + walletManagersFacade = walletManagersFacade, + appPreferencesStore = appPreferencesStore, + ) + } } \ No newline at end of file diff --git a/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultNetworksRepository.kt b/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultNetworksRepository.kt index 44096bdcd7..f25a65291c 100644 --- a/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultNetworksRepository.kt +++ b/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultNetworksRepository.kt @@ -26,6 +26,7 @@ import kotlinx.coroutines.flow.channelFlow import kotlinx.coroutines.flow.collectLatest import timber.log.Timber +@Suppress("LongParameterList") internal class DefaultNetworksRepository( private val networksStatusesStore: NetworksStatusesStore, private val walletManagersFacade: WalletManagersFacade, diff --git a/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultPolkadotAccountHealthCheckRepository.kt b/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultPolkadotAccountHealthCheckRepository.kt new file mode 100644 index 0000000000..e43b946f97 --- /dev/null +++ b/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultPolkadotAccountHealthCheckRepository.kt @@ -0,0 +1,169 @@ +package com.tangem.data.tokens.repository + +import com.tangem.blockchain.blockchains.polkadot.AccountCheckProvider +import com.tangem.blockchain.common.Blockchain +import com.tangem.blockchain.common.BlockchainSdkError +import com.tangem.datasource.local.preferences.AppPreferencesStore +import com.tangem.datasource.local.preferences.PreferencesKeys.POLKADOT_HEALTH_CHECKED_IMMUTABLE_ACCOUNTS +import com.tangem.datasource.local.preferences.PreferencesKeys.POLKADOT_HEALTH_CHECKED_RESET_ACCOUNTS +import com.tangem.datasource.local.preferences.PreferencesKeys.POLKADOT_HEALTH_CHECK_LAST_INDEXED_TX +import com.tangem.datasource.local.preferences.utils.getObjectListSync +import com.tangem.datasource.local.preferences.utils.getObjectMap +import com.tangem.domain.tokens.model.Network +import com.tangem.domain.tokens.repository.PolkadotAccountHealthCheckRepository +import com.tangem.domain.walletmanager.WalletManagersFacade +import com.tangem.domain.wallets.models.UserWalletId +import com.tangem.utils.extensions.addOrReplace +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.asSharedFlow +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock +import timber.log.Timber + +internal class DefaultPolkadotAccountHealthCheckRepository( + private val walletManagersFacade: WalletManagersFacade, + private val appPreferencesStore: AppPreferencesStore, +) : PolkadotAccountHealthCheckRepository { + + private val hasImmortalTransaction = MutableSharedFlow() + private val hasResetTransaction = MutableSharedFlow() + + private val mutex = Mutex() + + override suspend fun runCheck(userWalletId: UserWalletId, network: Network) { + // Run Polkadot account health check + if (Blockchain.fromId(network.id.value) != Blockchain.Polkadot) return + + mutex.withLock { + val walletManager = walletManagersFacade.getOrCreateWalletManager(userWalletId, network) + val accountCheckProvider = requireNotNull(walletManager as AccountCheckProvider) { + Timber.e("Unable to cast wallet manager to AccountCheckProvider") + return + } + val address = walletManager.wallet.address + + checkHasReset(accountCheckProvider, address) + checkHasImmortal(accountCheckProvider, address) + } + } + + override fun subscribeToHasImmortalResults() = hasImmortalTransaction.asSharedFlow() + + override fun subscribeToHasResetResults() = hasResetTransaction.asSharedFlow() + + private suspend fun checkHasReset(polkadotManager: AccountCheckProvider, address: String) { + val checkedAddresses = appPreferencesStore.getObjectListSync(POLKADOT_HEALTH_CHECKED_RESET_ACCOUNTS) + if (checkedAddresses.contains(address)) return + runCatching { + val accountInfo = requireNotNull(polkadotManager.getAccountInfo().account) { + Timber.e("Account info is null") + } + val nonce = accountInfo.nonce + val extrinsicCount = accountInfo.countExtrinsic + + // Account was reset + if (nonce != null && extrinsicCount != null) { + hasResetTransaction.emit(nonce < extrinsicCount) + updateCheckedResetAddressesList(address) + } + }.onFailure { + if ((it as? BlockchainSdkError.CustomError)?.customMessage == ACCOUNT_NOT_FOUND) { + updateCheckedResetAddressesList(address) + } + } + } + + private suspend fun checkHasImmortal(polkadotManager: AccountCheckProvider, address: String) { + val checkedAddresses = appPreferencesStore.getObjectListSync(POLKADOT_HEALTH_CHECKED_IMMUTABLE_ACCOUNTS) + if (checkedAddresses.contains(address)) return + + runCatching { + do { + // Getting batch of extrinsics to check + val lastChecked = appPreferencesStore.getObjectMap(POLKADOT_HEALTH_CHECK_LAST_INDEXED_TX) + val lastExtrinsic = lastChecked[address] + val extrinsicListResult = polkadotManager.getExtrinsicList(afterExtrinsicId = lastExtrinsic) + // Checking extrinsic one by one + extrinsicListResult.extrinsic?.forEach { tx -> + val hash = tx.hash + val id = tx.id + if (hash != null && id != null) { + val details = polkadotManager.getExtrinsicDetail(hash) + + // We found an `immortal` transaction + if (details.lifetime == null) { + hasImmortalTransaction.emit(true) + updateCheckedImmutableAddressesList(address) + clearLastCheckedTransaction(address) + return + } + + // Saving last checked transaction + updateLastCheckedTransaction(address, id) + } + } + } while (!extrinsicListResult.extrinsic.isNullOrEmpty()) + + // We checked all transactions up to current moment and did not found an `immortal` transaction + hasImmortalTransaction.emit(false) + updateCheckedImmutableAddressesList(address) + clearLastCheckedTransaction(address) + } + } + + private suspend fun updateLastCheckedTransaction(address: String, txId: Long) { + appPreferencesStore.editData { + val savedList = it.getObjectMap(POLKADOT_HEALTH_CHECK_LAST_INDEXED_TX) + val updatedList = savedList.toMutableMap() + updatedList[address] = txId + it.setObjectMap(POLKADOT_HEALTH_CHECK_LAST_INDEXED_TX, updatedList) + } + } + + private suspend fun clearLastCheckedTransaction(address: String) { + appPreferencesStore.editData { mutablePreferences -> + val savedList = mutablePreferences.getObjectMap(POLKADOT_HEALTH_CHECK_LAST_INDEXED_TX) + val updatedList = savedList.toMutableMap() + updatedList.remove(address) + mutablePreferences.setObjectMap(POLKADOT_HEALTH_CHECK_LAST_INDEXED_TX, updatedList) + } + } + + private suspend fun updateCheckedImmutableAddressesList(address: String) { + appPreferencesStore.editData { mutablePreferences -> + val savedList = mutablePreferences.getObjectList(POLKADOT_HEALTH_CHECKED_IMMUTABLE_ACCOUNTS) + val updatedList = savedList?.toMutableList() + updatedList?.addOrReplace(address) { it == address } + + if (updatedList != null) { + mutablePreferences.setObjectList( + POLKADOT_HEALTH_CHECKED_IMMUTABLE_ACCOUNTS, + updatedList.toList(), + ) + } else { + mutablePreferences.remove(POLKADOT_HEALTH_CHECKED_IMMUTABLE_ACCOUNTS) + } + } + } + + private suspend fun updateCheckedResetAddressesList(address: String) { + appPreferencesStore.editData { mutablePreferences -> + val savedList = mutablePreferences.getObjectList(POLKADOT_HEALTH_CHECKED_RESET_ACCOUNTS) + val updatedList = savedList?.toMutableList() + updatedList?.addOrReplace(address) { it == address } + + if (updatedList != null) { + mutablePreferences.setObjectList( + POLKADOT_HEALTH_CHECKED_RESET_ACCOUNTS, + updatedList.toList(), + ) + } else { + mutablePreferences.remove(POLKADOT_HEALTH_CHECKED_RESET_ACCOUNTS) + } + } + } + + private companion object { + const val ACCOUNT_NOT_FOUND = "Record Not Found" + } +} \ No newline at end of file diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetPolkadotCheckHasImmortalUseCase.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetPolkadotCheckHasImmortalUseCase.kt new file mode 100644 index 0000000000..04df6436d3 --- /dev/null +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetPolkadotCheckHasImmortalUseCase.kt @@ -0,0 +1,10 @@ +package com.tangem.domain.tokens + +import com.tangem.domain.tokens.repository.PolkadotAccountHealthCheckRepository +import kotlinx.coroutines.flow.Flow + +class GetPolkadotCheckHasImmortalUseCase( + private val polkadotAccountHealthCheckRepository: PolkadotAccountHealthCheckRepository, +) { + operator fun invoke(): Flow = polkadotAccountHealthCheckRepository.subscribeToHasImmortalResults() +} \ No newline at end of file diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetPolkadotCheckHasResetUseCase.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetPolkadotCheckHasResetUseCase.kt new file mode 100644 index 0000000000..b67e44d13b --- /dev/null +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetPolkadotCheckHasResetUseCase.kt @@ -0,0 +1,11 @@ +package com.tangem.domain.tokens + +import com.tangem.domain.tokens.repository.PolkadotAccountHealthCheckRepository +import kotlinx.coroutines.flow.Flow + +class GetPolkadotCheckHasResetUseCase( + private val polkadotAccountHealthCheckRepository: PolkadotAccountHealthCheckRepository, +) { + + operator fun invoke(): Flow = polkadotAccountHealthCheckRepository.subscribeToHasResetResults() +} \ No newline at end of file diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/RunPolkadotAccountHealthCheckUseCase.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/RunPolkadotAccountHealthCheckUseCase.kt new file mode 100644 index 0000000000..11f9080959 --- /dev/null +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/RunPolkadotAccountHealthCheckUseCase.kt @@ -0,0 +1,17 @@ +package com.tangem.domain.tokens + +import com.tangem.domain.tokens.model.Network +import com.tangem.domain.tokens.repository.PolkadotAccountHealthCheckRepository +import com.tangem.domain.wallets.models.UserWalletId +import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import kotlinx.coroutines.withContext + +class RunPolkadotAccountHealthCheckUseCase( + private val polkadotAccountHealthCheckRepository: PolkadotAccountHealthCheckRepository, + private val dispatchers: CoroutineDispatcherProvider, +) { + + suspend operator fun invoke(userWalletId: UserWalletId, network: Network) = withContext(dispatchers.io) { + polkadotAccountHealthCheckRepository.runCheck(userWalletId, network) + } +} \ No newline at end of file diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/PolkadotAccountHealthCheckRepository.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/PolkadotAccountHealthCheckRepository.kt new file mode 100644 index 0000000000..2da0aa6226 --- /dev/null +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/PolkadotAccountHealthCheckRepository.kt @@ -0,0 +1,14 @@ +package com.tangem.domain.tokens.repository + +import com.tangem.domain.tokens.model.Network +import com.tangem.domain.wallets.models.UserWalletId +import kotlinx.coroutines.flow.Flow + +interface PolkadotAccountHealthCheckRepository { + + suspend fun runCheck(userWalletId: UserWalletId, network: Network) + + fun subscribeToHasImmortalResults(): Flow + + fun subscribeToHasResetResults(): Flow +} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/analytics/WalletScreenAnalyticsEvent.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/analytics/WalletScreenAnalyticsEvent.kt index 088622ed27..04535b6a3b 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/analytics/WalletScreenAnalyticsEvent.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/analytics/WalletScreenAnalyticsEvent.kt @@ -23,7 +23,7 @@ sealed class WalletScreenAnalyticsEvent { override val oneTimeEventId: String = id + userWalletId.stringValue } - object WalletOpened : Basic(event = "Wallet Opened") + data object WalletOpened : Basic(event = "Wallet Opened") class CardWasScanned(source: AnalyticsParam.ScannedFrom) : Basic( event = "Card Was Scanned", @@ -48,13 +48,33 @@ sealed class WalletScreenAnalyticsEvent { ) } + sealed class Token( + event: String, + params: Map = mapOf(), + ) : AnalyticsEvent(category = "Token", event = event, params = params) { + + class PolkadotAccountReset(hasReset: Boolean) : Token( + event = "Polkadot Account Reset", + params = mapOf( + AnalyticsParam.STATE to if (hasReset) "Yes" else "No", + ), + ) + + class PolkadotImmortalTransactions(hasImmortalTransaction: Boolean) : Token( + event = "Polkadot Immortal Transactions", + params = mapOf( + AnalyticsParam.STATE to if (hasImmortalTransaction) "Yes" else "No", + ), + ) + } + sealed class MainScreen( event: String, params: Map = mapOf(), ) : AnalyticsEvent(category = "Main Screen", event = event, params = params) { - object ScreenOpened : MainScreen(event = "Screen opened") - object WalletSwipe : MainScreen(event = "Wallet Swipe") + data object ScreenOpened : MainScreen(event = "Screen opened") + data object WalletSwipe : MainScreen(event = "Wallet Swipe") class EnableBiometrics(state: AnalyticsParam.OnOffState) : MainScreen( event = "Enable Biometric", @@ -66,37 +86,37 @@ sealed class WalletScreenAnalyticsEvent { params = mapOf("Result" to result.value), ) - object NoticeBackupYourWalletTapped : MainScreen(event = "Notice - Backup Your Wallet Tapped") - object NoticeScanYourCardTapped : MainScreen(event = "Notice - Scan Your Card Tapped") - object NoticeWalletLocked : MainScreen(event = "Notice - Wallet Locked") - object WalletUnlockTapped : MainScreen(event = "Notice - Wallet Unlock Tapped") + data object NoticeBackupYourWalletTapped : MainScreen(event = "Notice - Backup Your Wallet Tapped") + data object NoticeScanYourCardTapped : MainScreen(event = "Notice - Scan Your Card Tapped") + data object NoticeWalletLocked : MainScreen(event = "Notice - Wallet Locked") + data object WalletUnlockTapped : MainScreen(event = "Notice - Wallet Unlock Tapped") - object NetworksUnreachable : MainScreen(event = "Notice - Networks Unreachable") + data object NetworksUnreachable : MainScreen(event = "Notice - Networks Unreachable") - object MissingAddresses : MainScreen(event = "Notice - Missing Addresses") + data object MissingAddresses : MainScreen(event = "Notice - Missing Addresses") - object CardSignedTransactions : MainScreen(event = "Notice - Card Signed Transactions") + data object CardSignedTransactions : MainScreen(event = "Notice - Card Signed Transactions") - object HowDoYouLikeTangem : MainScreen(event = "Notice - How Do You Like Tangem") + data object HowDoYouLikeTangem : MainScreen(event = "Notice - How Do You Like Tangem") - object ProductSampleCard : MainScreen(event = "Notice - Product Sample Card") + data object ProductSampleCard : MainScreen(event = "Notice - Product Sample Card") - object TestnetCard : MainScreen(event = "Notice - Testnet Card") + data object TestnetCard : MainScreen(event = "Notice - Testnet Card") - object DemoCard : MainScreen(event = "Notice - Demo Card") + data object DemoCard : MainScreen(event = "Notice - Demo Card") - object DevelopmentCard : MainScreen(event = "Notice - Development Card") + data object DevelopmentCard : MainScreen(event = "Notice - Development Card") - object WalletUnlock : MainScreen(event = "Notice - Wallet Unlock") + data object WalletUnlock : MainScreen(event = "Notice - Wallet Unlock") - object BackupYourWallet : MainScreen(event = "Notice - Backup Your Wallet") + data object BackupYourWallet : MainScreen(event = "Notice - Backup Your Wallet") - object UnlockAllWithBiometrics : MainScreen(event = "Button - Unlock All With Biometrics") + data object UnlockAllWithBiometrics : MainScreen(event = "Button - Unlock All With Biometrics") - object UnlockWithCardScan : MainScreen(event = "Button - Unlock With Card Scan") + data object UnlockWithCardScan : MainScreen(event = "Button - Unlock With Card Scan") - object EditWalletTapped : MainScreen(event = "Button - Edit Wallet Tapped") + data object EditWalletTapped : MainScreen(event = "Button - Edit Wallet Tapped") - object DeleteWalletTapped : MainScreen(event = "Button - Delete Wallet Tapped") + data object DeleteWalletTapped : MainScreen(event = "Button - Delete Wallet Tapped") } } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/MultiWalletContentLoader.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/MultiWalletContentLoader.kt index ba746cd830..bc6d40794b 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/MultiWalletContentLoader.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/MultiWalletContentLoader.kt @@ -4,6 +4,7 @@ import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase import com.tangem.domain.redux.ReduxStateHolder import com.tangem.domain.tokens.ApplyTokenListSortingUseCase import com.tangem.domain.tokens.GetTokenListUseCase +import com.tangem.domain.tokens.RunPolkadotAccountHealthCheckUseCase import com.tangem.domain.wallets.models.UserWallet import com.tangem.feature.wallet.presentation.wallet.analytics.utils.TokenListAnalyticsSender import com.tangem.feature.wallet.presentation.wallet.analytics.utils.WalletWarningsAnalyticsSender @@ -29,6 +30,7 @@ internal class MultiWalletContentLoader( private val applyTokenListSortingUseCase: ApplyTokenListSortingUseCase, private val getMultiWalletWarningsFactory: GetMultiWalletWarningsFactory, private val reduxStateHolder: ReduxStateHolder, + private val runPolkadotAccountHealthCheckUseCase: RunPolkadotAccountHealthCheckUseCase, ) : WalletContentLoader(id = userWallet.walletId) { override fun create(): List { @@ -42,6 +44,7 @@ internal class MultiWalletContentLoader( getTokenListUseCase = getTokenListUseCase, getSelectedAppCurrencyUseCase = getSelectedAppCurrencyUseCase, applyTokenListSortingUseCase = applyTokenListSortingUseCase, + runPolkadotAccountHealthCheckUseCase = runPolkadotAccountHealthCheckUseCase, ), MultiWalletWarningsSubscriber( userWallet = userWallet, diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/MultiWalletContentLoaderFactory.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/MultiWalletContentLoaderFactory.kt index 794574309a..540fdea589 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/MultiWalletContentLoaderFactory.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/MultiWalletContentLoaderFactory.kt @@ -4,6 +4,7 @@ import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase import com.tangem.domain.redux.ReduxStateHolder import com.tangem.domain.tokens.ApplyTokenListSortingUseCase import com.tangem.domain.tokens.GetTokenListUseCase +import com.tangem.domain.tokens.RunPolkadotAccountHealthCheckUseCase import com.tangem.domain.wallets.models.UserWallet import com.tangem.feature.wallet.presentation.wallet.analytics.utils.TokenListAnalyticsSender import com.tangem.feature.wallet.presentation.wallet.analytics.utils.WalletWarningsAnalyticsSender @@ -26,6 +27,7 @@ internal class MultiWalletContentLoaderFactory @Inject constructor( private val applyTokenListSortingUseCase: ApplyTokenListSortingUseCase, private val reduxStateHolder: ReduxStateHolder, private val walletWarningsAnalyticsSender: WalletWarningsAnalyticsSender, + private val runPolkadotAccountHealthCheckUseCase: RunPolkadotAccountHealthCheckUseCase, ) { fun create(userWallet: UserWallet, clickIntents: WalletClickIntents): WalletContentLoader { @@ -41,6 +43,7 @@ internal class MultiWalletContentLoaderFactory @Inject constructor( reduxStateHolder = reduxStateHolder, walletWarningsAnalyticsSender = walletWarningsAnalyticsSender, applyTokenListSortingUseCase = applyTokenListSortingUseCase, + runPolkadotAccountHealthCheckUseCase = runPolkadotAccountHealthCheckUseCase, ) } } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/SingleWalletWithTokenContentLoader.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/SingleWalletWithTokenContentLoader.kt index 525f4952ae..655c6255a3 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/SingleWalletWithTokenContentLoader.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/SingleWalletWithTokenContentLoader.kt @@ -2,6 +2,7 @@ package com.tangem.feature.wallet.presentation.wallet.loaders.implementors import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase import com.tangem.domain.tokens.GetCardTokensListUseCase +import com.tangem.domain.tokens.RunPolkadotAccountHealthCheckUseCase import com.tangem.domain.wallets.models.UserWallet import com.tangem.feature.wallet.presentation.wallet.analytics.utils.TokenListAnalyticsSender import com.tangem.feature.wallet.presentation.wallet.analytics.utils.WalletWarningsAnalyticsSender @@ -24,6 +25,7 @@ internal class SingleWalletWithTokenContentLoader( private val getMultiWalletWarningsFactory: GetMultiWalletWarningsFactory, private val getCardTokensListUseCase: GetCardTokensListUseCase, private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase, + private val runPolkadotAccountHealthCheckUseCase: RunPolkadotAccountHealthCheckUseCase, ) : WalletContentLoader(id = userWallet.walletId) { override fun create(): List { @@ -36,6 +38,7 @@ internal class SingleWalletWithTokenContentLoader( walletWithFundsChecker = walletWithFundsChecker, getCardTokensListUseCase = getCardTokensListUseCase, getSelectedAppCurrencyUseCase = getSelectedAppCurrencyUseCase, + runPolkadotAccountHealthCheckUseCase = runPolkadotAccountHealthCheckUseCase, ), MultiWalletWarningsSubscriber( userWallet = userWallet, diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/SingleWalletWithTokenContentLoaderFactory.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/SingleWalletWithTokenContentLoaderFactory.kt index e92241ad9a..e00b6fe74c 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/SingleWalletWithTokenContentLoaderFactory.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/SingleWalletWithTokenContentLoaderFactory.kt @@ -2,6 +2,7 @@ package com.tangem.feature.wallet.presentation.wallet.loaders.implementors import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase import com.tangem.domain.tokens.GetCardTokensListUseCase +import com.tangem.domain.tokens.RunPolkadotAccountHealthCheckUseCase import com.tangem.domain.wallets.models.UserWallet import com.tangem.feature.wallet.presentation.wallet.analytics.utils.TokenListAnalyticsSender import com.tangem.feature.wallet.presentation.wallet.analytics.utils.WalletWarningsAnalyticsSender @@ -21,6 +22,7 @@ internal class SingleWalletWithTokenContentLoaderFactory @Inject constructor( private val getCardTokensListUseCase: GetCardTokensListUseCase, private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase, private val walletWarningsAnalyticsSender: WalletWarningsAnalyticsSender, + private val runPolkadotAccountHealthCheckUseCase: RunPolkadotAccountHealthCheckUseCase, ) { fun create(userWallet: UserWallet, clickIntents: WalletClickIntents): SingleWalletWithTokenContentLoader { @@ -34,6 +36,7 @@ internal class SingleWalletWithTokenContentLoaderFactory @Inject constructor( getCardTokensListUseCase = getCardTokensListUseCase, getSelectedAppCurrencyUseCase = getSelectedAppCurrencyUseCase, walletWarningsAnalyticsSender = walletWarningsAnalyticsSender, + runPolkadotAccountHealthCheckUseCase = runPolkadotAccountHealthCheckUseCase, ) } } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/BasicTokenListSubscriber.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/BasicTokenListSubscriber.kt index 0dcd95aa6a..7dcd29e739 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/BasicTokenListSubscriber.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/BasicTokenListSubscriber.kt @@ -4,7 +4,9 @@ import arrow.core.Either import arrow.core.getOrElse import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase import com.tangem.domain.appcurrency.model.AppCurrency +import com.tangem.domain.tokens.RunPolkadotAccountHealthCheckUseCase import com.tangem.domain.tokens.error.TokenListError +import com.tangem.domain.tokens.model.NetworkGroup import com.tangem.domain.tokens.model.TokenList import com.tangem.domain.wallets.models.UserWallet import com.tangem.feature.wallet.presentation.wallet.analytics.utils.TokenListAnalyticsSender @@ -33,6 +35,7 @@ internal abstract class BasicTokenListSubscriber( private val tokenListAnalyticsSender: TokenListAnalyticsSender, private val walletWithFundsChecker: WalletWithFundsChecker, private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase, + private val runPolkadotAccountHealthCheckUseCase: RunPolkadotAccountHealthCheckUseCase, ) : WalletSubscriber() { private val sendAnalyticsJobHolder = JobHolder() @@ -53,6 +56,8 @@ internal abstract class BasicTokenListSubscriber( coroutineScope.launch { onTokenListReceived(maybeTokenList) }.saveIn(onTokenListReceivedJobHolder) + + coroutineScope.launch { startCheck(maybeTokenList) } }, flow2 = getSelectedAppCurrencyUseCase().distinctUntilChanged(), transform = { maybeTokenList, maybeAppCurrency -> @@ -72,6 +77,21 @@ internal abstract class BasicTokenListSubscriber( ) } + private suspend fun startCheck(maybeTokenList: Either) { + // Run Polkadot account health check + maybeTokenList.getOrNull()?.let { tokenList -> + val cryptoCurrencies = when (tokenList) { + is TokenList.GroupedByNetwork -> tokenList.groups.flatMap(NetworkGroup::currencies) + is TokenList.Ungrouped -> tokenList.currencies + is TokenList.Empty -> emptyList() + } + + cryptoCurrencies.forEach { + runPolkadotAccountHealthCheckUseCase(userWallet.walletId, it.currency.network) + } + } + } + protected open suspend fun onTokenListReceived(maybeTokenList: Either) { /* no-op */ } diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/MultiWalletTokenListSubscriber.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/MultiWalletTokenListSubscriber.kt index 9185370945..e3de1238a9 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/MultiWalletTokenListSubscriber.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/MultiWalletTokenListSubscriber.kt @@ -5,6 +5,7 @@ import arrow.core.getOrElse import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase import com.tangem.domain.tokens.ApplyTokenListSortingUseCase import com.tangem.domain.tokens.GetTokenListUseCase +import com.tangem.domain.tokens.RunPolkadotAccountHealthCheckUseCase import com.tangem.domain.tokens.error.TokenListError import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.tokens.model.TokenList @@ -24,6 +25,7 @@ internal class MultiWalletTokenListSubscriber( tokenListAnalyticsSender: TokenListAnalyticsSender, walletWithFundsChecker: WalletWithFundsChecker, getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase, + runPolkadotAccountHealthCheckUseCase: RunPolkadotAccountHealthCheckUseCase, ) : BasicTokenListSubscriber( userWallet = userWallet, stateHolder = stateHolder, @@ -31,6 +33,7 @@ internal class MultiWalletTokenListSubscriber( tokenListAnalyticsSender = tokenListAnalyticsSender, walletWithFundsChecker = walletWithFundsChecker, getSelectedAppCurrencyUseCase = getSelectedAppCurrencyUseCase, + runPolkadotAccountHealthCheckUseCase = runPolkadotAccountHealthCheckUseCase, ) { override fun tokenListFlow(): MaybeTokenListFlow = getTokenListUseCase(userWallet.walletId) diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/SingleWalletWithTokenListSubscriber.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/SingleWalletWithTokenListSubscriber.kt index 54c8ca07de..952fcb8c29 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/SingleWalletWithTokenListSubscriber.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/SingleWalletWithTokenListSubscriber.kt @@ -2,6 +2,7 @@ package com.tangem.feature.wallet.presentation.wallet.subscribers import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase import com.tangem.domain.tokens.GetCardTokensListUseCase +import com.tangem.domain.tokens.RunPolkadotAccountHealthCheckUseCase import com.tangem.domain.wallets.models.UserWallet import com.tangem.feature.wallet.presentation.wallet.analytics.utils.TokenListAnalyticsSender import com.tangem.feature.wallet.presentation.wallet.domain.WalletWithFundsChecker @@ -17,6 +18,7 @@ internal class SingleWalletWithTokenListSubscriber( tokenListAnalyticsSender: TokenListAnalyticsSender, walletWithFundsChecker: WalletWithFundsChecker, getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase, + runPolkadotAccountHealthCheckUseCase: RunPolkadotAccountHealthCheckUseCase, ) : BasicTokenListSubscriber( userWallet = userWallet, stateHolder = stateHolder, @@ -24,6 +26,7 @@ internal class SingleWalletWithTokenListSubscriber( tokenListAnalyticsSender = tokenListAnalyticsSender, walletWithFundsChecker = walletWithFundsChecker, getSelectedAppCurrencyUseCase = getSelectedAppCurrencyUseCase, + runPolkadotAccountHealthCheckUseCase = runPolkadotAccountHealthCheckUseCase, ) { override fun tokenListFlow(): MaybeTokenListFlow = getCardTokensListUseCase(userWallet.walletId) diff --git a/gradle/dependencies.toml b/gradle/dependencies.toml index 64ca08576f..838452da7b 100644 --- a/gradle/dependencies.toml +++ b/gradle/dependencies.toml @@ -85,7 +85,7 @@ web3j = "4.10.1" # endregion Other libraries # region Tangem -tangemBlockchainSdk = "release-app_5.8-536" +tangemBlockchainSdk = "release-app_5.8-542" #tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds tangemCardSdk = "release-app_5.8-338" #tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^ From 62a9dfc991288fb706f08614aad7218f2eb5c97b Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 28 Mar 2024 10:57:56 +0000 Subject: [PATCH 02/67] Updated on 2026-08-14 --- .../com/tangem/tap/common/DialogManager.kt | 2 +- .../tap/common/redux/global/GlobalAction.kt | 7 ++++- .../common/redux/global/GlobalMiddleware.kt | 30 ++++++++++++++----- .../tangem/tap/common/ui/ScanFailsDialog.kt | 12 ++++++-- .../scanCard/DefaultScanCardProcessor.kt | 8 ++--- .../domain/scanCard/LegacyScanProcessor.kt | 7 +++-- .../domain/scanCard/UseCaseScanProcessor.kt | 9 +++--- .../details/redux/DetailsMiddleware.kt | 3 +- .../details/ui/details/DetailsViewModel.kt | 3 +- .../tap/features/home/redux/HomeAction.kt | 5 ---- .../tap/features/home/redux/HomeMiddleware.kt | 8 ++--- .../onboarding/OnboardingMenuProvider.kt | 3 +- .../wallet/ui/OnboardingWalletFragment.kt | 3 +- .../ui/dialogs/WalletActivationErrorDialog.kt | 3 +- .../send/ui/dialogs/RequestFeeErrorDialog.kt | 3 +- .../ui/dialogs/SendTransactionFailsDialog.kt | 3 +- .../welcome/redux/WelcomeMiddleware.kt | 2 +- .../core/analytics/models/AnalyticsParam.kt | 12 ++++---- .../com/tangem/core/analytics/models/Basic.kt | 9 ++++-- .../com/tangem/core/navigation/StateDialog.kt | 6 +++- .../tangem/domain/card/ScanCardProcessor.kt | 4 +-- .../router/DefaultWalletRouter.kt | 6 +++- .../analytics/WalletScreenAnalyticsEvent.kt | 2 +- .../intents/WalletWarningsClickIntents.kt | 2 +- 24 files changed, 98 insertions(+), 54 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/common/DialogManager.kt b/app/src/main/java/com/tangem/tap/common/DialogManager.kt index 4f4494f2bc..3339239daa 100644 --- a/app/src/main/java/com/tangem/tap/common/DialogManager.kt +++ b/app/src/main/java/com/tangem/tap/common/DialogManager.kt @@ -46,7 +46,7 @@ class DialogManager : StoreSubscriber { dialog = when (state.dialog) { is AppDialog.SimpleOkDialogRes -> SimpleOkDialog.create(state.dialog, context) - is StateDialog.ScanFailsDialog -> ScanFailsDialog.create(context) + is StateDialog.ScanFailsDialog -> ScanFailsDialog.create(context, state.dialog.source) is AppDialog.AddressInfoDialog -> AddressInfoBottomSheetDialog(state.dialog, context) is AppDialog.TestActionsDialog -> TestActionsBottomSheetDialog(state.dialog, context) is AppDialog.RussianCardholdersWarningDialog -> RussianCardholdersWarningBottomSheetDialog( diff --git a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalAction.kt b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalAction.kt index e3b376b612..a9c23d448f 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalAction.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalAction.kt @@ -2,6 +2,7 @@ package com.tangem.tap.common.redux.global import com.tangem.blockchain.common.WalletManager import com.tangem.common.CompletionResult +import com.tangem.core.analytics.models.AnalyticsParam import com.tangem.core.navigation.StateDialog import com.tangem.datasource.config.ConfigManager import com.tangem.datasource.config.models.ChatConfig @@ -51,7 +52,11 @@ sealed class GlobalAction : Action { } object ScanFailsCounter { - data class ChooseBehavior(val result: CompletionResult) : GlobalAction() + data class ChooseBehavior( + val result: CompletionResult, + val analyticsSource: AnalyticsParam.ScreensSources, + ) : GlobalAction() + object Reset : GlobalAction() object Increment : GlobalAction() } diff --git a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalMiddleware.kt b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalMiddleware.kt index 79d6ed3a69..921898e383 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalMiddleware.kt @@ -4,6 +4,7 @@ import com.tangem.common.CompletionResult import com.tangem.common.core.TangemSdkError import com.tangem.common.extensions.guard import com.tangem.core.analytics.Analytics +import com.tangem.core.analytics.models.AnalyticsParam import com.tangem.core.analytics.models.Basic import com.tangem.core.navigation.StateDialog import com.tangem.datasource.config.models.Config @@ -54,14 +55,7 @@ private fun handleAction(action: Action, appState: () -> AppState?) { when (action.result) { is CompletionResult.Success -> store.dispatch(GlobalAction.ScanFailsCounter.Reset) is CompletionResult.Failure -> { - if (action.result.error is TangemSdkError.UserCancelled) { - store.dispatch(GlobalAction.ScanFailsCounter.Increment) - if (store.state.globalState.scanCardFailsCounter >= 2) { - store.dispatchDialogShow(StateDialog.ScanFailsDialog) - } - } else { - store.dispatch(GlobalAction.ScanFailsCounter.Reset) - } + handleFailureChooseBehaviour(action.result, action.analyticsSource) } } } @@ -184,6 +178,26 @@ private fun handleAction(action: Action, appState: () -> AppState?) { } } +private fun handleFailureChooseBehaviour( + result: CompletionResult.Failure, + analyticsSource: AnalyticsParam.ScreensSources, +) { + if (result.error is TangemSdkError.UserCancelled) { + store.dispatch(GlobalAction.ScanFailsCounter.Increment) + if (store.state.globalState.scanCardFailsCounter >= 2) { + val scanFailsSource = when (analyticsSource) { + is AnalyticsParam.ScreensSources.SignIn -> StateDialog.ScanFailsSource.SIGN_IN + is AnalyticsParam.ScreensSources.Settings -> StateDialog.ScanFailsSource.SETTINGS + is AnalyticsParam.ScreensSources.Intro -> StateDialog.ScanFailsSource.INTRO + else -> StateDialog.ScanFailsSource.MAIN + } + store.dispatchDialogShow(StateDialog.ScanFailsDialog(scanFailsSource)) + } + } else { + store.dispatch(GlobalAction.ScanFailsCounter.Reset) + } +} + private fun restoreAppCurrency() { scope.launch { val currency = store.inject(DaggerGraphState::appCurrencyRepository) diff --git a/app/src/main/java/com/tangem/tap/common/ui/ScanFailsDialog.kt b/app/src/main/java/com/tangem/tap/common/ui/ScanFailsDialog.kt index 70ad852f71..d59f668687 100644 --- a/app/src/main/java/com/tangem/tap/common/ui/ScanFailsDialog.kt +++ b/app/src/main/java/com/tangem/tap/common/ui/ScanFailsDialog.kt @@ -4,7 +4,9 @@ import android.content.Context import androidx.appcompat.app.AlertDialog import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.tangem.core.analytics.Analytics +import com.tangem.core.analytics.models.AnalyticsParam import com.tangem.core.analytics.models.Basic +import com.tangem.core.navigation.StateDialog import com.tangem.tap.common.extensions.dispatchDialogHide import com.tangem.tap.common.feedback.ScanFailsEmail import com.tangem.tap.common.redux.global.GlobalAction @@ -15,12 +17,18 @@ import com.tangem.wallet.R [REDACTED_AUTHOR] */ object ScanFailsDialog { - fun create(context: Context): AlertDialog { + fun create(context: Context, source: StateDialog.ScanFailsSource): AlertDialog { return MaterialAlertDialogBuilder(context, R.style.CustomMaterialDialog).apply { setTitle(context.getString(R.string.common_warning)) setMessage(R.string.alert_troubleshooting_scan_card_title) setPositiveButton(R.string.alert_button_request_support) { _, _ -> - Analytics.send(Basic.ButtonSupport()) + val sourceAnalytics = when (source) { + StateDialog.ScanFailsSource.MAIN -> AnalyticsParam.ScreensSources.Main + StateDialog.ScanFailsSource.SIGN_IN -> AnalyticsParam.ScreensSources.SignIn + StateDialog.ScanFailsSource.SETTINGS -> AnalyticsParam.ScreensSources.Settings + StateDialog.ScanFailsSource.INTRO -> AnalyticsParam.ScreensSources.Intro + } + Analytics.send(Basic.ButtonSupport(sourceAnalytics)) store.dispatch(GlobalAction.SendEmail(ScanFailsEmail())) } setNeutralButton(R.string.common_cancel) { _, _ -> } diff --git a/app/src/main/java/com/tangem/tap/domain/scanCard/DefaultScanCardProcessor.kt b/app/src/main/java/com/tangem/tap/domain/scanCard/DefaultScanCardProcessor.kt index 1bac186d83..c72d366b37 100644 --- a/app/src/main/java/com/tangem/tap/domain/scanCard/DefaultScanCardProcessor.kt +++ b/app/src/main/java/com/tangem/tap/domain/scanCard/DefaultScanCardProcessor.kt @@ -2,7 +2,7 @@ package com.tangem.tap.domain.scanCard import com.tangem.common.CompletionResult import com.tangem.common.core.TangemError -import com.tangem.core.analytics.models.AnalyticsEvent +import com.tangem.core.analytics.models.AnalyticsParam import com.tangem.domain.card.ScanCardProcessor import com.tangem.domain.models.scan.ScanResponse import com.tangem.tap.common.extensions.inject @@ -27,7 +27,7 @@ internal class DefaultScanCardProcessor : ScanCardProcessor { @Suppress("LongParameterList") override suspend fun scan( - analyticsEvent: AnalyticsEvent?, + analyticsSource: AnalyticsParam.ScreensSources, cardId: String?, onProgressStateChange: suspend (showProgress: Boolean) -> Unit, onScanStateChange: suspend (scanInProgress: Boolean) -> Unit, @@ -38,7 +38,7 @@ internal class DefaultScanCardProcessor : ScanCardProcessor { ) { if (isNewCardScanningEnabled) { UseCaseScanProcessor.scan( - analyticsEvent, + analyticsSource, cardId, onProgressStateChange, onScanStateChange, @@ -49,7 +49,7 @@ internal class DefaultScanCardProcessor : ScanCardProcessor { ) } else { LegacyScanProcessor.scan( - analyticsEvent, + analyticsSource, cardId, onProgressStateChange, onScanStateChange, diff --git a/app/src/main/java/com/tangem/tap/domain/scanCard/LegacyScanProcessor.kt b/app/src/main/java/com/tangem/tap/domain/scanCard/LegacyScanProcessor.kt index 757e78cb34..9164a9bfd6 100644 --- a/app/src/main/java/com/tangem/tap/domain/scanCard/LegacyScanProcessor.kt +++ b/app/src/main/java/com/tangem/tap/domain/scanCard/LegacyScanProcessor.kt @@ -7,6 +7,8 @@ import com.tangem.common.doOnFailure import com.tangem.common.doOnSuccess import com.tangem.core.analytics.Analytics import com.tangem.core.analytics.models.AnalyticsEvent +import com.tangem.core.analytics.models.AnalyticsParam +import com.tangem.core.analytics.models.Basic import com.tangem.core.navigation.AppScreen import com.tangem.core.navigation.NavigationAction import com.tangem.domain.common.TapWorkarounds.canSkipBackup @@ -45,7 +47,7 @@ internal object LegacyScanProcessor { @Suppress("LongParameterList") suspend fun scan( - analyticsEvent: AnalyticsEvent?, + analyticsSource: AnalyticsParam.ScreensSources, cardId: String?, onProgressStateChange: suspend (showProgress: Boolean) -> Unit, onScanStateChange: suspend (scanInProgress: Boolean) -> Unit, @@ -61,7 +63,8 @@ internal object LegacyScanProcessor { val result = tangemSdkManager.scanProduct(cardId) - store.dispatchOnMain(GlobalAction.ScanFailsCounter.ChooseBehavior(result)) + val analyticsEvent = Basic.CardWasScanned(analyticsSource) + store.dispatchOnMain(GlobalAction.ScanFailsCounter.ChooseBehavior(result, analyticsSource)) result .doOnFailure { error -> diff --git a/app/src/main/java/com/tangem/tap/domain/scanCard/UseCaseScanProcessor.kt b/app/src/main/java/com/tangem/tap/domain/scanCard/UseCaseScanProcessor.kt index 02e1701894..2afbd6d813 100644 --- a/app/src/main/java/com/tangem/tap/domain/scanCard/UseCaseScanProcessor.kt +++ b/app/src/main/java/com/tangem/tap/domain/scanCard/UseCaseScanProcessor.kt @@ -3,7 +3,8 @@ package com.tangem.tap.domain.scanCard import arrow.fx.coroutines.resourceScope import com.tangem.common.CompletionResult import com.tangem.common.core.TangemError -import com.tangem.core.analytics.models.AnalyticsEvent +import com.tangem.core.analytics.models.AnalyticsParam +import com.tangem.core.analytics.models.Basic import com.tangem.core.navigation.AppScreen import com.tangem.core.navigation.NavigationAction import com.tangem.domain.card.ScanCardException @@ -34,7 +35,7 @@ internal object UseCaseScanProcessor { @Suppress("LongParameterList") suspend fun scan( - analyticsEvent: AnalyticsEvent?, + analyticsSource: AnalyticsParam.ScreensSources, cardId: String?, onProgressStateChange: suspend (showProgress: Boolean) -> Unit, onScanStateChange: suspend (scanInProgress: Boolean) -> Unit, @@ -48,9 +49,7 @@ internal object UseCaseScanProcessor { val scanCardUseCase = store.inject(DaggerGraphState::scanCardUseCase) val chains = buildList { add(ScanningFinishedChain { onScanStateChange(false) }) - if (analyticsEvent != null) { - add(AnalyticsChain(analyticsEvent)) - } + add(AnalyticsChain(Basic.CardWasScanned(analyticsSource))) add(DisclaimerChain(store, disclaimerWillShow)) add(CheckForOnboardingChain(store, store.state.globalState.tapWalletManager)) } diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt index 90aa10f2cd..654edd4102 100644 --- a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt @@ -7,7 +7,6 @@ import com.tangem.common.core.TangemSdkError import com.tangem.common.core.UserCodeRequestPolicy import com.tangem.common.extensions.guard import com.tangem.core.analytics.Analytics -import com.tangem.core.analytics.models.Basic import com.tangem.core.navigation.AppScreen import com.tangem.core.navigation.NavigationAction import com.tangem.core.ui.extensions.TextReference @@ -580,7 +579,7 @@ class DetailsMiddleware { ) store.inject(DaggerGraphState::scanCardProcessor).scan( - analyticsEvent = Basic.CardWasScanned(CoreAnalyticsParam.ScannedFrom.MyWallets), + analyticsSource = CoreAnalyticsParam.ScreensSources.Settings, onWalletNotCreated = { // No need to rollback policy, continue with the policy set before the card scan store.dispatchWithMain(DetailsAction.ScanAndSaveUserWallet.Success) diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsViewModel.kt b/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsViewModel.kt index 12698bcde4..5ce6bc642a 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsViewModel.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsViewModel.kt @@ -4,6 +4,7 @@ import androidx.compose.runtime.MutableState import androidx.compose.runtime.mutableStateOf import com.tangem.common.extensions.guard import com.tangem.core.analytics.Analytics +import com.tangem.core.analytics.models.AnalyticsParam import com.tangem.core.analytics.models.Basic import com.tangem.core.navigation.AppScreen import com.tangem.core.navigation.NavigationAction @@ -138,7 +139,7 @@ internal class DetailsViewModel( } private fun sendFeedback() { - Analytics.send(Basic.ButtonSupport()) + Analytics.send(Basic.ButtonSupport(AnalyticsParam.ScreensSources.Settings)) store.dispatchOnMain(GlobalAction.SendEmail(FeedbackEmail())) } diff --git a/app/src/main/java/com/tangem/tap/features/home/redux/HomeAction.kt b/app/src/main/java/com/tangem/tap/features/home/redux/HomeAction.kt index e87ba1ee99..987d3d51a5 100644 --- a/app/src/main/java/com/tangem/tap/features/home/redux/HomeAction.kt +++ b/app/src/main/java/com/tangem/tap/features/home/redux/HomeAction.kt @@ -1,8 +1,5 @@ package com.tangem.tap.features.home.redux -import com.tangem.core.analytics.models.AnalyticsEvent -import com.tangem.core.analytics.models.AnalyticsParam -import com.tangem.core.analytics.models.Basic import com.tangem.tap.common.entities.IndeterminateProgressButton import kotlinx.coroutines.CoroutineScope import org.rekotlin.Action @@ -17,11 +14,9 @@ sealed class HomeAction : Action { /** * Action for scanning card * - * @property analyticsEvent analytics event * @property scope lifecycle scope. It will be canceled when lifecycle-aware component is destroyed */ data class ReadCard( - val analyticsEvent: AnalyticsEvent? = Basic.CardWasScanned(AnalyticsParam.ScannedFrom.Introduction), val scope: CoroutineScope, ) : HomeAction() diff --git a/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt b/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt index be5b026c2d..9a936a74e8 100644 --- a/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt @@ -5,7 +5,7 @@ import com.tangem.common.doOnResult import com.tangem.common.doOnSuccess import com.tangem.common.extensions.guard import com.tangem.core.analytics.Analytics -import com.tangem.core.analytics.models.AnalyticsEvent +import com.tangem.core.analytics.models.AnalyticsParam import com.tangem.core.analytics.models.Basic import com.tangem.core.navigation.AppScreen import com.tangem.core.navigation.NavigationAction @@ -60,7 +60,7 @@ private fun handleHomeAction(action: Action) { } is HomeAction.ReadCard -> { action.scope.launch { - readCard(action.analyticsEvent) + readCard() } } is HomeAction.GoToShop -> { @@ -76,13 +76,13 @@ private fun handleHomeAction(action: Action) { } } -private suspend fun readCard(analyticsEvent: AnalyticsEvent?) { +private suspend fun readCard() { store.inject(DaggerGraphState::cardSdkConfigRepository).setAccessCodeRequestPolicy( isBiometricsRequestPolicy = preferencesStorage.shouldSaveAccessCodes, ) store.inject(DaggerGraphState::scanCardProcessor).scan( - analyticsEvent = analyticsEvent, + analyticsSource = AnalyticsParam.ScreensSources.Intro, onProgressStateChange = { showProgress -> if (showProgress) { changeButtonState(ButtonState.PROGRESS) diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingMenuProvider.kt b/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingMenuProvider.kt index 936a202358..5e0028b00d 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingMenuProvider.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingMenuProvider.kt @@ -5,6 +5,7 @@ import android.view.MenuInflater import android.view.MenuItem import androidx.core.view.MenuProvider import com.tangem.core.analytics.Analytics +import com.tangem.core.analytics.models.AnalyticsParam import com.tangem.core.analytics.models.Basic import com.tangem.tap.common.feedback.SupportInfo import com.tangem.tap.common.redux.global.GlobalAction @@ -21,7 +22,7 @@ class OnboardingMenuProvider : MenuProvider { override fun onMenuItemSelected(menuItem: MenuItem): Boolean = when (menuItem.itemId) { R.id.menu_item_chat_support -> { - Analytics.send(Basic.ButtonSupport()) + Analytics.send(Basic.ButtonSupport(AnalyticsParam.ScreensSources.Intro)) // changed on email support [REDACTED_TASK_KEY] store.dispatch(GlobalAction.SendEmail(SupportInfo())) true diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/OnboardingWalletFragment.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/OnboardingWalletFragment.kt index 0655d01aa9..f7e6ab244e 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/OnboardingWalletFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/OnboardingWalletFragment.kt @@ -22,6 +22,7 @@ import com.tangem.common.CardIdFormatter import com.tangem.common.CompletionResult import com.tangem.common.core.CardIdDisplayFormat import com.tangem.core.analytics.Analytics +import com.tangem.core.analytics.models.AnalyticsParam import com.tangem.core.analytics.models.Basic import com.tangem.core.ui.extensions.setStatusBarColor import com.tangem.domain.common.util.cardTypesResolver @@ -502,7 +503,7 @@ class OnboardingWalletFragment : private fun makeSeedPhraseRouter(): SeedPhraseRouter = SeedPhraseRouter( onBack = ::legacyOnBackHandler, onOpenChat = { - Analytics.send(Basic.ButtonSupport()) + Analytics.send(Basic.ButtonSupport(AnalyticsParam.ScreensSources.Intro)) // changed on email support [REDACTED_TASK_KEY] store.dispatch(GlobalAction.SendEmail(SupportInfo())) }, diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/dialogs/WalletActivationErrorDialog.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/dialogs/WalletActivationErrorDialog.kt index ddec9cdfb3..ace9edcf19 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/dialogs/WalletActivationErrorDialog.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/dialogs/WalletActivationErrorDialog.kt @@ -4,6 +4,7 @@ import android.app.Dialog import android.content.Context import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.tangem.core.analytics.Analytics +import com.tangem.core.analytics.models.AnalyticsParam import com.tangem.core.analytics.models.Basic import com.tangem.tap.common.extensions.dispatchDialogHide import com.tangem.tap.common.feedback.SupportInfo @@ -21,7 +22,7 @@ object WalletActivationErrorDialog { setPositiveButton(R.string.common_ok) { _, _ -> dialog.onConfirm() } setNegativeButton(R.string.common_support) { _, _ -> // changed on email support [REDACTED_TASK_KEY] - Analytics.send(Basic.ButtonSupport()) + Analytics.send(Basic.ButtonSupport(AnalyticsParam.ScreensSources.Intro)) store.dispatch(GlobalAction.SendEmail(SupportInfo())) } setOnDismissListener { store.dispatchDialogHide() } diff --git a/app/src/main/java/com/tangem/tap/features/send/ui/dialogs/RequestFeeErrorDialog.kt b/app/src/main/java/com/tangem/tap/features/send/ui/dialogs/RequestFeeErrorDialog.kt index 9cf4359f72..cf27394348 100644 --- a/app/src/main/java/com/tangem/tap/features/send/ui/dialogs/RequestFeeErrorDialog.kt +++ b/app/src/main/java/com/tangem/tap/features/send/ui/dialogs/RequestFeeErrorDialog.kt @@ -3,6 +3,7 @@ package com.tangem.tap.features.send.ui.dialogs import android.content.Context import androidx.appcompat.app.AlertDialog import com.tangem.core.analytics.Analytics +import com.tangem.core.analytics.models.AnalyticsParam import com.tangem.core.analytics.models.Basic import com.tangem.tap.common.feedback.SendTransactionFailedEmail import com.tangem.tap.common.redux.global.GlobalAction @@ -21,7 +22,7 @@ object RequestFeeErrorDialog { setTitle(R.string.common_fee_error) setMessage(context.getString(R.string.alert_failed_to_send_transaction_message, errorMessage)) setNegativeButton(R.string.details_row_title_contact_to_support) { _, _ -> - Analytics.send(Basic.ButtonSupport()) + Analytics.send(Basic.ButtonSupport(AnalyticsParam.ScreensSources.Send)) store.dispatch(GlobalAction.SendEmail(SendTransactionFailedEmail(errorMessage))) } setPositiveButton(R.string.common_retry) { _, _ -> dialog.onRetry() } diff --git a/app/src/main/java/com/tangem/tap/features/send/ui/dialogs/SendTransactionFailsDialog.kt b/app/src/main/java/com/tangem/tap/features/send/ui/dialogs/SendTransactionFailsDialog.kt index f862ff5e94..5687caa590 100644 --- a/app/src/main/java/com/tangem/tap/features/send/ui/dialogs/SendTransactionFailsDialog.kt +++ b/app/src/main/java/com/tangem/tap/features/send/ui/dialogs/SendTransactionFailsDialog.kt @@ -6,6 +6,7 @@ import com.tangem.blockchain.common.Blockchain import com.tangem.blockchain.common.BlockchainSdkError import com.tangem.common.module.ModuleMessageConverter import com.tangem.core.analytics.Analytics +import com.tangem.core.analytics.models.AnalyticsParam import com.tangem.core.analytics.models.Basic import com.tangem.sdk.extensions.localizedDescription import com.tangem.tap.common.extensions.stripZeroPlainString @@ -33,7 +34,7 @@ object SendTransactionFailsDialog { setTitle(R.string.alert_failed_to_send_transaction_title) setMessage(context.getString(R.string.alert_failed_to_send_transaction_message, errorMessage)) setNeutralButton(R.string.details_row_title_contact_to_support) { _, _ -> - Analytics.send(Basic.ButtonSupport()) + Analytics.send(Basic.ButtonSupport(AnalyticsParam.ScreensSources.Send)) store.dispatch(GlobalAction.SendEmail(SendTransactionFailedEmail(errorMessage))) } setPositiveButton(R.string.common_cancel) { _, _ -> } diff --git a/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeMiddleware.kt b/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeMiddleware.kt index e2ff46f387..638776a6ed 100644 --- a/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeMiddleware.kt @@ -172,7 +172,7 @@ internal class WelcomeMiddleware { isBiometricsRequestPolicy = preferencesStorage.shouldSaveAccessCodes, ) store.inject(DaggerGraphState::scanCardProcessor).scan( - analyticsEvent = Basic.CardWasScanned(AnalyticsParam.ScannedFrom.SignIn), + analyticsSource = AnalyticsParam.ScreensSources.SignIn, onSuccess = { scanResponse -> scope.launch { onCardScanned(scanResponse) } }, diff --git a/core/analytics/models/src/main/java/com/tangem/core/analytics/models/AnalyticsParam.kt b/core/analytics/models/src/main/java/com/tangem/core/analytics/models/AnalyticsParam.kt index 5f53ab40d7..a20223d2cb 100644 --- a/core/analytics/models/src/main/java/com/tangem/core/analytics/models/AnalyticsParam.kt +++ b/core/analytics/models/src/main/java/com/tangem/core/analytics/models/AnalyticsParam.kt @@ -57,11 +57,13 @@ sealed class AnalyticsParam { object BlockchainSdk : Error("Blockchain Sdk Error") } - sealed class ScannedFrom(val value: String) { - object Introduction : ScannedFrom("Introduction") - object Main : ScannedFrom("Main") - object SignIn : ScannedFrom("Sign In") - object MyWallets : ScannedFrom("My Wallets") + sealed class ScreensSources(val value: String) { + data object Settings : ScreensSources("Settings") + data object Main : ScreensSources("Main") + data object SignIn : ScreensSources("Sign In") + data object Send : ScreensSources("Send") + data object Intro : ScreensSources("Introduction") + data object MyWallets : ScreensSources("My Wallets") } sealed class TxSentFrom(val value: String) { diff --git a/core/analytics/models/src/main/java/com/tangem/core/analytics/models/Basic.kt b/core/analytics/models/src/main/java/com/tangem/core/analytics/models/Basic.kt index 8820f8ff12..9f39ae5bdd 100644 --- a/core/analytics/models/src/main/java/com/tangem/core/analytics/models/Basic.kt +++ b/core/analytics/models/src/main/java/com/tangem/core/analytics/models/Basic.kt @@ -7,7 +7,7 @@ sealed class Basic( ) : AnalyticsEvent("Basic", event, params, error) { class CardWasScanned( - source: AnalyticsParam.ScannedFrom, + source: AnalyticsParam.ScreensSources, ) : Basic( event = "Card Was Scanned", params = mapOf( @@ -76,5 +76,10 @@ sealed class Basic( class WalletOpened : Basic(event = "Wallet Opened") - class ButtonSupport : Basic("Request Support") + class ButtonSupport(source: AnalyticsParam.ScreensSources) : Basic( + event = "Request Support", + params = mapOf( + AnalyticsParam.SOURCE to source.value, + ), + ) } \ No newline at end of file diff --git a/core/navigation/src/main/java/com/tangem/core/navigation/StateDialog.kt b/core/navigation/src/main/java/com/tangem/core/navigation/StateDialog.kt index 416307b644..75aa1f0579 100644 --- a/core/navigation/src/main/java/com/tangem/core/navigation/StateDialog.kt +++ b/core/navigation/src/main/java/com/tangem/core/navigation/StateDialog.kt @@ -2,5 +2,9 @@ package com.tangem.core.navigation interface StateDialog { - object ScanFailsDialog : StateDialog + data class ScanFailsDialog(val source: ScanFailsSource) : StateDialog + + enum class ScanFailsSource { + MAIN, SIGN_IN, SETTINGS, INTRO; + } } \ No newline at end of file diff --git a/domain/card/src/main/kotlin/com/tangem/domain/card/ScanCardProcessor.kt b/domain/card/src/main/kotlin/com/tangem/domain/card/ScanCardProcessor.kt index 961d9acd07..5b3824d63e 100644 --- a/domain/card/src/main/kotlin/com/tangem/domain/card/ScanCardProcessor.kt +++ b/domain/card/src/main/kotlin/com/tangem/domain/card/ScanCardProcessor.kt @@ -2,7 +2,7 @@ package com.tangem.domain.card import com.tangem.common.CompletionResult import com.tangem.common.core.TangemError -import com.tangem.core.analytics.models.AnalyticsEvent +import com.tangem.core.analytics.models.AnalyticsParam import com.tangem.domain.models.scan.ScanResponse interface ScanCardProcessor { @@ -13,7 +13,7 @@ interface ScanCardProcessor { ): CompletionResult suspend fun scan( - analyticsEvent: AnalyticsEvent? = null, + analyticsSource: AnalyticsParam.ScreensSources, cardId: String? = null, onProgressStateChange: suspend (showProgress: Boolean) -> Unit = {}, onScanStateChange: suspend (scanInProgress: Boolean) -> Unit = {}, diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/router/DefaultWalletRouter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/router/DefaultWalletRouter.kt index 5ae3d34992..c0f4910bfe 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/router/DefaultWalletRouter.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/router/DefaultWalletRouter.kt @@ -163,7 +163,11 @@ internal class DefaultWalletRouter( } override fun openScanFailedDialog() { - reduxNavController.navigate(action = NavigationAction.OpenDialog(StateDialog.ScanFailsDialog)) + reduxNavController.navigate( + action = NavigationAction.OpenDialog( + StateDialog.ScanFailsDialog(StateDialog.ScanFailsSource.MAIN), + ), + ) } private companion object { diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/analytics/WalletScreenAnalyticsEvent.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/analytics/WalletScreenAnalyticsEvent.kt index 088622ed27..8153718e8e 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/analytics/WalletScreenAnalyticsEvent.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/analytics/WalletScreenAnalyticsEvent.kt @@ -25,7 +25,7 @@ sealed class WalletScreenAnalyticsEvent { object WalletOpened : Basic(event = "Wallet Opened") - class CardWasScanned(source: AnalyticsParam.ScannedFrom) : Basic( + class CardWasScanned(source: AnalyticsParam.ScreensSources) : Basic( event = "Card Was Scanned", params = mapOf( AnalyticsParam.SOURCE to source.value, diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletWarningsClickIntents.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletWarningsClickIntents.kt index 1ff5dc6191..553dafc78b 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletWarningsClickIntents.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletWarningsClickIntents.kt @@ -107,7 +107,7 @@ internal class WalletWarningsClickIntentsImplementor @Inject constructor( } override fun onGenerateMissedAddressesClick(missedAddressCurrencies: List) { - analyticsEventHandler.send(Basic.CardWasScanned(AnalyticsParam.ScannedFrom.Main)) + analyticsEventHandler.send(Basic.CardWasScanned(AnalyticsParam.ScreensSources.Main)) analyticsEventHandler.send(MainScreen.NoticeScanYourCardTapped) viewModelScope.launch(dispatchers.main) { From 4c77120575cdb747ae435a39a7194d735a69ef0e Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 1 Apr 2024 11:11:12 +0500 Subject: [PATCH 03/67] Updated on 2026-08-14 --- .../tokens/RunPolkadotAccountHealthCheckUseCase.kt | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/RunPolkadotAccountHealthCheckUseCase.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/RunPolkadotAccountHealthCheckUseCase.kt index 11f9080959..8afa023e85 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/RunPolkadotAccountHealthCheckUseCase.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/RunPolkadotAccountHealthCheckUseCase.kt @@ -1,5 +1,6 @@ package com.tangem.domain.tokens +import arrow.core.Either import com.tangem.domain.tokens.model.Network import com.tangem.domain.tokens.repository.PolkadotAccountHealthCheckRepository import com.tangem.domain.wallets.models.UserWalletId @@ -11,7 +12,10 @@ class RunPolkadotAccountHealthCheckUseCase( private val dispatchers: CoroutineDispatcherProvider, ) { - suspend operator fun invoke(userWalletId: UserWalletId, network: Network) = withContext(dispatchers.io) { - polkadotAccountHealthCheckRepository.runCheck(userWalletId, network) - } + suspend operator fun invoke(userWalletId: UserWalletId, network: Network): Either = + withContext(dispatchers.io) { + Either.catch { + polkadotAccountHealthCheckRepository.runCheck(userWalletId, network) + } + } } \ No newline at end of file From 1e6fc15c6a90a51789a4b86256ba911a3cbe1a3d Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 1 Apr 2024 15:39:25 +0400 Subject: [PATCH 04/67] Updated on 2026-08-14 --- app/src/main/java/com/tangem/tap/LockUserWalletsTimer.kt | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/LockUserWalletsTimer.kt b/app/src/main/java/com/tangem/tap/LockUserWalletsTimer.kt index 256bd62a5f..e30b53c765 100644 --- a/app/src/main/java/com/tangem/tap/LockUserWalletsTimer.kt +++ b/app/src/main/java/com/tangem/tap/LockUserWalletsTimer.kt @@ -7,6 +7,7 @@ import com.tangem.core.navigation.AppScreen import com.tangem.core.navigation.NavigationAction import com.tangem.domain.wallets.legacy.asLockable import com.tangem.tap.common.extensions.dispatchOnMain +import com.tangem.tap.common.extensions.dispatchWithMain import kotlinx.coroutines.* import timber.log.Timber import kotlin.time.Duration @@ -103,12 +104,14 @@ internal class LockUserWalletsTimer( |- Millis passed: ${currentTime - startTime} """.trimIndent(), ) - userWalletsListManager.lock() + if (preferencesStorage.wasApplicationStopped) { preferencesStorage.shouldOpenWelcomeScreenOnResume = true } else { - store.dispatchOnMain(NavigationAction.PopBackTo(AppScreen.Welcome)) + store.dispatchWithMain(NavigationAction.PopBackTo(AppScreen.Welcome)) } + + userWalletsListManager.lock() } } } From 788a4718710fa7456a40b44eff73b8473abfb662 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 3 Apr 2024 11:07:52 +0100 Subject: [PATCH 05/67] Updated on 2026-08-14 --- gradle/dependencies.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gradle/dependencies.toml b/gradle/dependencies.toml index 42119bfeb6..8731df13aa 100644 --- a/gradle/dependencies.toml +++ b/gradle/dependencies.toml @@ -85,9 +85,9 @@ leakcanary = "2.13" # endregion Other libraries # region Tangem -tangemBlockchainSdk = "develop-547" +tangemBlockchainSdk = "release-app_5.9-560" #tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds -tangemCardSdk = "develop-339" +tangemCardSdk = "release-app_5.9-340" #tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^ # endregion Tangem From cc07b0ba263fb04f4e10359b40b7f044d697ad75 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 3 Apr 2024 15:39:22 +0500 Subject: [PATCH 06/67] Updated on 2026-08-14 --- .../main/java/com/tangem/tap/MainActivity.kt | 10 +- .../tap/di/domain/TokensDomainModule.kt | 3 +- .../local/preferences/AppPreferencesStore.kt | 12 ++ .../local/preferences/PreferencesKeys.kt | 6 +- .../utils/AppPreferencesStoreExt.kt | 9 + .../tangem/data/tokens/di/TokensDataModule.kt | 2 + ...ultPolkadotAccountHealthCheckRepository.kt | 158 ++++++++++-------- .../GetPolkadotCheckHasImmortalUseCase.kt | 3 +- .../tokens/GetPolkadotCheckHasResetUseCase.kt | 3 +- .../RunPolkadotAccountHealthCheckUseCase.kt | 12 +- .../PolkadotAccountHealthCheckRepository.kt | 4 +- gradle/dependencies.toml | 2 +- 12 files changed, 129 insertions(+), 95 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/MainActivity.kt b/app/src/main/java/com/tangem/tap/MainActivity.kt index 3a06ab01ef..2a453f6cc8 100644 --- a/app/src/main/java/com/tangem/tap/MainActivity.kt +++ b/app/src/main/java/com/tangem/tap/MainActivity.kt @@ -503,13 +503,19 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac lifecycleScope.launch { getPolkadotCheckHasResetUseCase() .flowWithLifecycle(lifecycle, minActiveState = Lifecycle.State.CREATED) + .distinctUntilChanged() .collect { - analyticsEventsHandler.send(WalletScreenAnalyticsEvent.Token.PolkadotAccountReset(it)) + analyticsEventsHandler.send(WalletScreenAnalyticsEvent.Token.PolkadotAccountReset(it.second)) } + } + lifecycleScope.launch { getPolkadotCheckHasImmortalUseCase() .flowWithLifecycle(lifecycle, minActiveState = Lifecycle.State.CREATED) + .distinctUntilChanged() .collect { - analyticsEventsHandler.send(WalletScreenAnalyticsEvent.Token.PolkadotImmortalTransactions(it)) + analyticsEventsHandler.send( + WalletScreenAnalyticsEvent.Token.PolkadotImmortalTransactions(it.second), + ) } } } diff --git a/app/src/main/java/com/tangem/tap/di/domain/TokensDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/TokensDomainModule.kt index 8f377b67ad..2e373e7c69 100644 --- a/app/src/main/java/com/tangem/tap/di/domain/TokensDomainModule.kt +++ b/app/src/main/java/com/tangem/tap/di/domain/TokensDomainModule.kt @@ -337,8 +337,7 @@ internal object TokensDomainModule { @ViewModelScoped fun provideRunPolkadotAccountHealthCheckUseCase( repository: PolkadotAccountHealthCheckRepository, - dispatchers: CoroutineDispatcherProvider, ): RunPolkadotAccountHealthCheckUseCase { - return RunPolkadotAccountHealthCheckUseCase(repository, dispatchers) + return RunPolkadotAccountHealthCheckUseCase(repository) } } \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/preferences/AppPreferencesStore.kt b/core/datasource/src/main/java/com/tangem/datasource/local/preferences/AppPreferencesStore.kt index 2b1de6b7b0..905fe4f767 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/local/preferences/AppPreferencesStore.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/local/preferences/AppPreferencesStore.kt @@ -72,6 +72,12 @@ class AppPreferencesStore( return this[key]?.let(adapter::fromJson).orEmpty() } + /** Get set of data [T] by string [key] */ + inline fun MutablePreferences.getObjectSet(key: Preferences.Key): Set? { + val adapter = moshi.adapter>(Types.newParameterizedType(Set::class.java, T::class.java)) + return this[key]?.let(adapter::fromJson) + } + /** * Set data [T] by string [key] to [MutablePreferences] * @@ -97,4 +103,10 @@ class AppPreferencesStore( this[key] = adapter.toJson(value) } + + /** Sets set of data [T] by string [key] to [MutablePreferences] */ + inline fun MutablePreferences.setObjectSet(key: Preferences.Key, value: Set) { + val adapter = moshi.adapter>(Types.newParameterizedType(Set::class.java, T::class.java)) + this[key] = adapter.toJson(value) + } } \ No newline at end of file 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 464387831e..2a6b0457d3 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 @@ -63,13 +63,13 @@ object PreferencesKeys { val APP_LOGS_KEY by lazy { stringPreferencesKey(name = "app_logs") } - val POLKADOT_HEALTH_CHECK_LAST_INDEXED_TX by lazy { + val POLKADOT_HEALTH_CHECK_LAST_INDEXED_TX_KEY by lazy { stringPreferencesKey(name = "POLKADOT_HEALTH_CHECK_LAST_INDEXED_TX") } - val POLKADOT_HEALTH_CHECKED_RESET_ACCOUNTS by lazy { + val POLKADOT_HEALTH_CHECKED_RESET_ACCOUNTS_KEY by lazy { stringPreferencesKey(name = "POLKADOT_HEALTH_CHECKED_RESET_ACCOUNTS") } - val POLKADOT_HEALTH_CHECKED_IMMUTABLE_ACCOUNTS by lazy { + val POLKADOT_HEALTH_CHECKED_IMMUTABLE_ACCOUNTS_KEY by lazy { stringPreferencesKey(name = "POLKADOT_HEALTH_CHECKED_IMMUTABLE_ACCOUNTS") } diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/preferences/utils/AppPreferencesStoreExt.kt b/core/datasource/src/main/java/com/tangem/datasource/local/preferences/utils/AppPreferencesStoreExt.kt index cd1facee55..aa3171803c 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/local/preferences/utils/AppPreferencesStoreExt.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/local/preferences/utils/AppPreferencesStoreExt.kt @@ -128,6 +128,15 @@ suspend inline fun AppPreferencesStore.getObjectMap(key: Preferences val type = Types.newParameterizedType(Map::class.java, String::class.java, V::class.java) val adapter = moshi.adapter>(type) + return data.firstOrNull() + ?.get(key) + ?.let(adapter::fromJson) + .orEmpty() +} + +/** Get set of data [T] by string [key], or empty if data is not found */ +suspend inline fun AppPreferencesStore.getObjectSetSync(key: Preferences.Key): Set { + val adapter = moshi.adapter>(Types.newParameterizedType(Set::class.java, T::class.java)) return data.firstOrNull() ?.get(key) ?.let(adapter::fromJson) diff --git a/data/tokens/src/main/kotlin/com/tangem/data/tokens/di/TokensDataModule.kt b/data/tokens/src/main/kotlin/com/tangem/data/tokens/di/TokensDataModule.kt index ef0aef1c7e..062692cd06 100644 --- a/data/tokens/src/main/kotlin/com/tangem/data/tokens/di/TokensDataModule.kt +++ b/data/tokens/src/main/kotlin/com/tangem/data/tokens/di/TokensDataModule.kt @@ -125,10 +125,12 @@ internal object TokensDataModule { fun providePolkadotAccountHealthCheckRepository( walletManagersFacade: WalletManagersFacade, appPreferencesStore: AppPreferencesStore, + dispatchers: CoroutineDispatcherProvider, ): PolkadotAccountHealthCheckRepository { return DefaultPolkadotAccountHealthCheckRepository( walletManagersFacade = walletManagersFacade, appPreferencesStore = appPreferencesStore, + dispatchers = dispatchers, ) } } \ No newline at end of file diff --git a/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultPolkadotAccountHealthCheckRepository.kt b/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultPolkadotAccountHealthCheckRepository.kt index e43b946f97..049991280b 100644 --- a/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultPolkadotAccountHealthCheckRepository.kt +++ b/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultPolkadotAccountHealthCheckRepository.kt @@ -1,49 +1,64 @@ package com.tangem.data.tokens.repository +import androidx.datastore.preferences.core.Preferences import com.tangem.blockchain.blockchains.polkadot.AccountCheckProvider +import com.tangem.blockchain.blockchains.polkadot.network.accounthealthcheck.ExtrinsicListItemResponse import com.tangem.blockchain.common.Blockchain import com.tangem.blockchain.common.BlockchainSdkError import com.tangem.datasource.local.preferences.AppPreferencesStore -import com.tangem.datasource.local.preferences.PreferencesKeys.POLKADOT_HEALTH_CHECKED_IMMUTABLE_ACCOUNTS -import com.tangem.datasource.local.preferences.PreferencesKeys.POLKADOT_HEALTH_CHECKED_RESET_ACCOUNTS -import com.tangem.datasource.local.preferences.PreferencesKeys.POLKADOT_HEALTH_CHECK_LAST_INDEXED_TX -import com.tangem.datasource.local.preferences.utils.getObjectListSync +import com.tangem.datasource.local.preferences.PreferencesKeys.POLKADOT_HEALTH_CHECKED_IMMUTABLE_ACCOUNTS_KEY +import com.tangem.datasource.local.preferences.PreferencesKeys.POLKADOT_HEALTH_CHECKED_RESET_ACCOUNTS_KEY +import com.tangem.datasource.local.preferences.PreferencesKeys.POLKADOT_HEALTH_CHECK_LAST_INDEXED_TX_KEY import com.tangem.datasource.local.preferences.utils.getObjectMap +import com.tangem.datasource.local.preferences.utils.getObjectSetSync import com.tangem.domain.tokens.model.Network import com.tangem.domain.tokens.repository.PolkadotAccountHealthCheckRepository import com.tangem.domain.walletmanager.WalletManagersFacade import com.tangem.domain.wallets.models.UserWalletId -import com.tangem.utils.extensions.addOrReplace +import com.tangem.utils.coroutines.CoroutineDispatcherProvider import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock +import kotlinx.coroutines.withContext import timber.log.Timber +import java.util.concurrent.ConcurrentHashMap +import kotlin.collections.set internal class DefaultPolkadotAccountHealthCheckRepository( private val walletManagersFacade: WalletManagersFacade, private val appPreferencesStore: AppPreferencesStore, + private val dispatchers: CoroutineDispatcherProvider, ) : PolkadotAccountHealthCheckRepository { - private val hasImmortalTransaction = MutableSharedFlow() - private val hasResetTransaction = MutableSharedFlow() + private val hasImmortalTransaction = MutableSharedFlow>() + private val hasResetTransaction = MutableSharedFlow>() private val mutex = Mutex() + private val mutexes = ConcurrentHashMap() override suspend fun runCheck(userWalletId: UserWalletId, network: Network) { // Run Polkadot account health check if (Blockchain.fromId(network.id.value) != Blockchain.Polkadot) return - mutex.withLock { - val walletManager = walletManagersFacade.getOrCreateWalletManager(userWalletId, network) - val accountCheckProvider = requireNotNull(walletManager as AccountCheckProvider) { - Timber.e("Unable to cast wallet manager to AccountCheckProvider") - return - } - val address = walletManager.wallet.address + val walletManager = walletManagersFacade.getOrCreateWalletManager(userWalletId, network) + val address = requireNotNull(walletManager?.wallet?.address) { + Timber.e("Address is null") + return + } + val accountCheckProvider = requireNotNull(walletManager as? AccountCheckProvider) { + Timber.e("Unable to cast wallet manager to AccountCheckProvider") + return + } - checkHasReset(accountCheckProvider, address) - checkHasImmortal(accountCheckProvider, address) + // use a separate mutexForKey for each key to avoid multiple calls block() to the same key + // also used mutex to safe create mutexForKey, otherwise it can lead to multiple calls for the same key + val mutexForKey = mutex.withLock { mutexes.getOrPut(address) { Mutex() } } + mutexForKey.withLock { + withContext(dispatchers.io) { + checkHasReset(accountCheckProvider, address) + checkHasImmortal(accountCheckProvider, address) + } } } @@ -52,7 +67,7 @@ internal class DefaultPolkadotAccountHealthCheckRepository( override fun subscribeToHasResetResults() = hasResetTransaction.asSharedFlow() private suspend fun checkHasReset(polkadotManager: AccountCheckProvider, address: String) { - val checkedAddresses = appPreferencesStore.getObjectListSync(POLKADOT_HEALTH_CHECKED_RESET_ACCOUNTS) + val checkedAddresses = appPreferencesStore.getObjectSetSync(POLKADOT_HEALTH_CHECKED_RESET_ACCOUNTS_KEY) if (checkedAddresses.contains(address)) return runCatching { val accountInfo = requireNotNull(polkadotManager.getAccountInfo().account) { @@ -63,102 +78,97 @@ internal class DefaultPolkadotAccountHealthCheckRepository( // Account was reset if (nonce != null && extrinsicCount != null) { - hasResetTransaction.emit(nonce < extrinsicCount) - updateCheckedResetAddressesList(address) + val hasReset = nonce < extrinsicCount + updateChecked(address, POLKADOT_HEALTH_CHECKED_RESET_ACCOUNTS_KEY) + hasResetTransaction.emit(address to hasReset) } }.onFailure { if ((it as? BlockchainSdkError.CustomError)?.customMessage == ACCOUNT_NOT_FOUND) { - updateCheckedResetAddressesList(address) + updateChecked(address, POLKADOT_HEALTH_CHECKED_RESET_ACCOUNTS_KEY) } } } - private suspend fun checkHasImmortal(polkadotManager: AccountCheckProvider, address: String) { - val checkedAddresses = appPreferencesStore.getObjectListSync(POLKADOT_HEALTH_CHECKED_IMMUTABLE_ACCOUNTS) + private suspend fun checkHasImmortal(accountCheckerProvider: AccountCheckProvider, address: String) { + val checkedAddresses = + appPreferencesStore.getObjectSetSync(POLKADOT_HEALTH_CHECKED_IMMUTABLE_ACCOUNTS_KEY) if (checkedAddresses.contains(address)) return runCatching { do { // Getting batch of extrinsics to check - val lastChecked = appPreferencesStore.getObjectMap(POLKADOT_HEALTH_CHECK_LAST_INDEXED_TX) + val lastChecked = appPreferencesStore.getObjectMap(POLKADOT_HEALTH_CHECK_LAST_INDEXED_TX_KEY) val lastExtrinsic = lastChecked[address] - val extrinsicListResult = polkadotManager.getExtrinsicList(afterExtrinsicId = lastExtrinsic) - // Checking extrinsic one by one - extrinsicListResult.extrinsic?.forEach { tx -> - val hash = tx.hash - val id = tx.id - if (hash != null && id != null) { - val details = polkadotManager.getExtrinsicDetail(hash) - - // We found an `immortal` transaction - if (details.lifetime == null) { - hasImmortalTransaction.emit(true) - updateCheckedImmutableAddressesList(address) - clearLastCheckedTransaction(address) - return - } - - // Saving last checked transaction - updateLastCheckedTransaction(address, id) + val extrinsicListResult = accountCheckerProvider.getExtrinsicList(afterExtrinsicId = lastExtrinsic) + extrinsicListResult.extrinsic + ?.forEach { tx -> + // Checking extrinsic one by one + if (checkTx(tx, address, accountCheckerProvider)) return } - } } while (!extrinsicListResult.extrinsic.isNullOrEmpty()) // We checked all transactions up to current moment and did not found an `immortal` transaction - hasImmortalTransaction.emit(false) - updateCheckedImmutableAddressesList(address) + hasImmortalTransaction.emit(address to false) + updateChecked(address, POLKADOT_HEALTH_CHECKED_IMMUTABLE_ACCOUNTS_KEY) clearLastCheckedTransaction(address) } } + private suspend fun checkTx( + tx: ExtrinsicListItemResponse, + address: String, + accountCheckerProvider: AccountCheckProvider, + ): Boolean { + val hash = tx.hash + val id = tx.id + if (hash != null && id != null) { + val details = accountCheckerProvider.getExtrinsicDetail(hash) + + // We found an `immortal` transaction + if (details.lifetime == null) { + hasImmortalTransaction.emit(address to true) + updateChecked(address, POLKADOT_HEALTH_CHECKED_IMMUTABLE_ACCOUNTS_KEY) + clearLastCheckedTransaction(address) + return true + } + + // Saving last checked transaction + updateLastCheckedTransaction(address, id) + } + return false + } + private suspend fun updateLastCheckedTransaction(address: String, txId: Long) { appPreferencesStore.editData { - val savedList = it.getObjectMap(POLKADOT_HEALTH_CHECK_LAST_INDEXED_TX) + val savedList = it.getObjectMap(POLKADOT_HEALTH_CHECK_LAST_INDEXED_TX_KEY) val updatedList = savedList.toMutableMap() updatedList[address] = txId - it.setObjectMap(POLKADOT_HEALTH_CHECK_LAST_INDEXED_TX, updatedList) + it.setObjectMap(POLKADOT_HEALTH_CHECK_LAST_INDEXED_TX_KEY, updatedList) } } private suspend fun clearLastCheckedTransaction(address: String) { appPreferencesStore.editData { mutablePreferences -> - val savedList = mutablePreferences.getObjectMap(POLKADOT_HEALTH_CHECK_LAST_INDEXED_TX) + val savedList = mutablePreferences.getObjectMap(POLKADOT_HEALTH_CHECK_LAST_INDEXED_TX_KEY) val updatedList = savedList.toMutableMap() updatedList.remove(address) - mutablePreferences.setObjectMap(POLKADOT_HEALTH_CHECK_LAST_INDEXED_TX, updatedList) + mutablePreferences.setObjectMap(POLKADOT_HEALTH_CHECK_LAST_INDEXED_TX_KEY, updatedList) } } - private suspend fun updateCheckedImmutableAddressesList(address: String) { + private suspend fun updateChecked(address: String, key: Preferences.Key) { appPreferencesStore.editData { mutablePreferences -> - val savedList = mutablePreferences.getObjectList(POLKADOT_HEALTH_CHECKED_IMMUTABLE_ACCOUNTS) - val updatedList = savedList?.toMutableList() - updatedList?.addOrReplace(address) { it == address } + val savedList = mutablePreferences.getObjectSet(key) + val updatedList = savedList?.toMutableSet() ?: mutableSetOf() + updatedList.add(address) - if (updatedList != null) { - mutablePreferences.setObjectList( - POLKADOT_HEALTH_CHECKED_IMMUTABLE_ACCOUNTS, - updatedList.toList(), + if (updatedList.isNotEmpty()) { + mutablePreferences.setObjectSet( + key, + updatedList.toSet(), ) } else { - mutablePreferences.remove(POLKADOT_HEALTH_CHECKED_IMMUTABLE_ACCOUNTS) - } - } - } - - private suspend fun updateCheckedResetAddressesList(address: String) { - appPreferencesStore.editData { mutablePreferences -> - val savedList = mutablePreferences.getObjectList(POLKADOT_HEALTH_CHECKED_RESET_ACCOUNTS) - val updatedList = savedList?.toMutableList() - updatedList?.addOrReplace(address) { it == address } - - if (updatedList != null) { - mutablePreferences.setObjectList( - POLKADOT_HEALTH_CHECKED_RESET_ACCOUNTS, - updatedList.toList(), - ) - } else { - mutablePreferences.remove(POLKADOT_HEALTH_CHECKED_RESET_ACCOUNTS) + mutablePreferences.remove(key) } } } diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetPolkadotCheckHasImmortalUseCase.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetPolkadotCheckHasImmortalUseCase.kt index 04df6436d3..527f6e1703 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetPolkadotCheckHasImmortalUseCase.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetPolkadotCheckHasImmortalUseCase.kt @@ -6,5 +6,6 @@ import kotlinx.coroutines.flow.Flow class GetPolkadotCheckHasImmortalUseCase( private val polkadotAccountHealthCheckRepository: PolkadotAccountHealthCheckRepository, ) { - operator fun invoke(): Flow = polkadotAccountHealthCheckRepository.subscribeToHasImmortalResults() + operator fun invoke(): Flow> = + polkadotAccountHealthCheckRepository.subscribeToHasImmortalResults() } \ No newline at end of file diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetPolkadotCheckHasResetUseCase.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetPolkadotCheckHasResetUseCase.kt index b67e44d13b..33ce7e960e 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetPolkadotCheckHasResetUseCase.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetPolkadotCheckHasResetUseCase.kt @@ -7,5 +7,6 @@ class GetPolkadotCheckHasResetUseCase( private val polkadotAccountHealthCheckRepository: PolkadotAccountHealthCheckRepository, ) { - operator fun invoke(): Flow = polkadotAccountHealthCheckRepository.subscribeToHasResetResults() + operator fun invoke(): Flow> = + polkadotAccountHealthCheckRepository.subscribeToHasResetResults() } \ No newline at end of file diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/RunPolkadotAccountHealthCheckUseCase.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/RunPolkadotAccountHealthCheckUseCase.kt index 8afa023e85..f7b3131361 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/RunPolkadotAccountHealthCheckUseCase.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/RunPolkadotAccountHealthCheckUseCase.kt @@ -4,18 +4,12 @@ import arrow.core.Either import com.tangem.domain.tokens.model.Network import com.tangem.domain.tokens.repository.PolkadotAccountHealthCheckRepository import com.tangem.domain.wallets.models.UserWalletId -import com.tangem.utils.coroutines.CoroutineDispatcherProvider -import kotlinx.coroutines.withContext class RunPolkadotAccountHealthCheckUseCase( private val polkadotAccountHealthCheckRepository: PolkadotAccountHealthCheckRepository, - private val dispatchers: CoroutineDispatcherProvider, ) { - suspend operator fun invoke(userWalletId: UserWalletId, network: Network): Either = - withContext(dispatchers.io) { - Either.catch { - polkadotAccountHealthCheckRepository.runCheck(userWalletId, network) - } - } + suspend operator fun invoke(userWalletId: UserWalletId, network: Network): Either = Either.catch { + polkadotAccountHealthCheckRepository.runCheck(userWalletId, network) + } } \ No newline at end of file diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/PolkadotAccountHealthCheckRepository.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/PolkadotAccountHealthCheckRepository.kt index 2da0aa6226..a0cd15dd47 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/PolkadotAccountHealthCheckRepository.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/PolkadotAccountHealthCheckRepository.kt @@ -8,7 +8,7 @@ interface PolkadotAccountHealthCheckRepository { suspend fun runCheck(userWalletId: UserWalletId, network: Network) - fun subscribeToHasImmortalResults(): Flow + fun subscribeToHasImmortalResults(): Flow> - fun subscribeToHasResetResults(): Flow + fun subscribeToHasResetResults(): Flow> } \ No newline at end of file diff --git a/gradle/dependencies.toml b/gradle/dependencies.toml index 838452da7b..6430f26a99 100644 --- a/gradle/dependencies.toml +++ b/gradle/dependencies.toml @@ -85,7 +85,7 @@ web3j = "4.10.1" # endregion Other libraries # region Tangem -tangemBlockchainSdk = "release-app_5.8-542" +tangemBlockchainSdk = "release-app_5.8-562" #tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds tangemCardSdk = "release-app_5.8-338" #tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^ From 4ce03a5d25726f0f34a4cff7e47e603201a05c9d Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 3 Apr 2024 16:33:11 +0400 Subject: [PATCH 07/67] Updated on 2026-08-14 --- gradle/dependencies.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/dependencies.toml b/gradle/dependencies.toml index 64ca08576f..d7d7d20333 100644 --- a/gradle/dependencies.toml +++ b/gradle/dependencies.toml @@ -87,7 +87,7 @@ web3j = "4.10.1" # region Tangem tangemBlockchainSdk = "release-app_5.8-536" #tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds -tangemCardSdk = "release-app_5.8-338" +tangemCardSdk = "release-app_5.8-341" #tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^ # endregion Tangem From 7e1e05e72a8fca916cafb6b6319ee76b2565a485 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 3 Apr 2024 20:32:44 +0500 Subject: [PATCH 08/67] Updated on 2026-08-14 --- .../presentation/viewmodel/SendViewModel.kt | 50 +++++++++++-------- 1 file changed, 28 insertions(+), 22 deletions(-) diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendViewModel.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendViewModel.kt index 67e152dae9..54b90feac1 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendViewModel.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendViewModel.kt @@ -32,7 +32,6 @@ import com.tangem.domain.transaction.usecase.SendTransactionUseCase import com.tangem.domain.txhistory.models.TxHistoryItem import com.tangem.domain.txhistory.usecase.GetExplorerTransactionUrlUseCase import com.tangem.domain.txhistory.usecase.GetFixedTxHistoryItemsUseCase -import com.tangem.domain.walletmanager.WalletManagersFacade import com.tangem.domain.wallets.models.UserWallet import com.tangem.domain.wallets.models.UserWalletId import com.tangem.domain.wallets.usecase.GetUserWalletUseCase @@ -75,15 +74,15 @@ internal class SendViewModel @Inject constructor( private val getNetworkCoinStatusUseCase: GetNetworkCoinStatusUseCase, private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase, private val getWalletsUseCase: GetWalletsUseCase, - private val getCryptoCurrenciesUseCase: GetCryptoCurrenciesUseCase, private val getPrimaryCurrencyStatusUpdatesUseCase: GetPrimaryCurrencyStatusUpdatesUseCase, private val getFeePaidCryptoCurrencyStatusSyncUseCase: GetFeePaidCryptoCurrencyStatusSyncUseCase, + private val getCryptoCurrencyStatusSyncUseCase: GetCryptoCurrencyStatusSyncUseCase, + private val getCryptoCurrencyStatusesSyncUseCase: GetCryptoCurrencyStatusesSyncUseCase, private val getFixedTxHistoryItemsUseCase: GetFixedTxHistoryItemsUseCase, private val getFeeUseCase: GetFeeUseCase, private val sendTransactionUseCase: SendTransactionUseCase, private val createTransactionUseCase: CreateTransactionUseCase, private val validateWalletAddressUseCase: ValidateWalletAddressUseCase, - private val walletManagersFacade: WalletManagersFacade, private val reduxStateHolder: ReduxStateHolder, private val isAmountSubtractAvailableUseCase: IsAmountSubtractAvailableUseCase, private val getBalanceHidingSettingsUseCase: GetBalanceHidingSettingsUseCase, @@ -403,31 +402,38 @@ internal class SendViewModel @Inject constructor( .filterNot { it.walletId == userWalletId || it.isLocked } .map { wallet -> async(dispatchers.io) { - getCryptoCurrenciesUseCase.getSync(wallet.walletId) - .fold( - ifRight = { currencyItem -> - val walletCurrency = currencyItem.firstOrNull { - it.network.id == cryptoCurrency.network.id - } ?: return@fold null - val addresses = walletManagersFacade.getAddress( - userWalletId = wallet.walletId, - network = walletCurrency.network, - ) - return@fold addresses.firstOrNull()?.let { - AvailableWallet( - name = wallet.name, - address = it.value, - ) - } - }, - ifLeft = { null }, - ) + wallet.toAvailableWallet() } } }.awaitAll() } } + private suspend fun UserWallet.toAvailableWallet(): AvailableWallet? { + return if (!isMultiCurrency) { + val status = getCryptoCurrencyStatusSyncUseCase(walletId).getOrNull() + val address = status?.value?.networkAddress + address?.let { + AvailableWallet( + name = name, + address = it.defaultAddress.value, + ) + } + } else { + val statuses = getCryptoCurrencyStatusesSyncUseCase(walletId).getOrNull() + val walletCurrency = statuses?.firstOrNull { + it.currency.network.id == cryptoCurrency.network.id + } + val address = walletCurrency?.value?.networkAddress + address?.let { + AvailableWallet( + name = name, + address = it.defaultAddress.value, + ) + } + } + } + private fun getTxHistory(): Flow> { return getFixedTxHistoryItemsUseCase( userWalletId = userWalletId, From 28b202b48dea4265db272a111d913ee2b402df3c Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 4 Apr 2024 13:36:35 +0500 Subject: [PATCH 09/67] Updated on 2026-08-14 --- .../repository/DefaultTxHistoryRepository.kt | 26 ++++++++++++------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/data/txhistory/src/main/kotlin/com/tangem/data/txhistory/repository/DefaultTxHistoryRepository.kt b/data/txhistory/src/main/kotlin/com/tangem/data/txhistory/repository/DefaultTxHistoryRepository.kt index 9f4801ff8c..dba0ed594a 100644 --- a/data/txhistory/src/main/kotlin/com/tangem/data/txhistory/repository/DefaultTxHistoryRepository.kt +++ b/data/txhistory/src/main/kotlin/com/tangem/data/txhistory/repository/DefaultTxHistoryRepository.kt @@ -21,6 +21,7 @@ import com.tangem.domain.walletmanager.utils.SdkPageConverter import com.tangem.domain.wallets.models.UserWallet import com.tangem.domain.wallets.models.UserWalletId import kotlinx.coroutines.flow.Flow +import timber.log.Timber class DefaultTxHistoryRepository( private val cacheRegistry: CacheRegistry, @@ -82,16 +83,21 @@ class DefaultTxHistoryRepository( pageSize: Int, refresh: Boolean, ): List { - cacheRegistry.invokeOnExpire( - key = getTxHistoryPageKey(currency, userWalletId, Page.Initial), - skipCache = refresh, - block = { fetchFixedSizeTxHistoryItems(userWalletId, currency, pageSize) }, - ) - val txs = txHistoryItemsStore.getSyncOrNull( - key = TxHistoryItemsStore.Key(userWalletId, currency), - page = Page.Initial, - )?.items - return txs ?: emptyList() + return try { + cacheRegistry.invokeOnExpire( + key = getTxHistoryPageKey(currency, userWalletId, Page.Initial), + skipCache = refresh, + block = { fetchFixedSizeTxHistoryItems(userWalletId, currency, pageSize) }, + ) + val txs = txHistoryItemsStore.getSyncOrNull( + key = TxHistoryItemsStore.Key(userWalletId, currency), + page = Page.Initial, + )?.items + txs ?: emptyList() + } catch (e: Throwable) { + Timber.e(e, "Unable to load the transaction history for the requested page: ${Page.Initial}") + emptyList() + } } private fun getTxHistoryPageKey(currency: CryptoCurrency, userWalletId: UserWalletId, page: Page): String { From ae432b0fd1c11dbd06259ec8d129d8a942ad45ec Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 4 Apr 2024 15:11:44 +0500 Subject: [PATCH 10/67] Updated on 2026-08-14 --- .../send/impl/presentation/ui/SendScreen.kt | 1 + .../ui/recipient/SendRecipientContent.kt | 124 ++++++++++++------ 2 files changed, 83 insertions(+), 42 deletions(-) diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/SendScreen.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/SendScreen.kt index b227fe07fe..fe28791ec9 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/SendScreen.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/SendScreen.kt @@ -122,6 +122,7 @@ private fun SendScreenContent(uiState: SendUiState, currentState: SendUiCurrentS SendUiStateType.Recipient -> SendRecipientContent( uiState = uiState.recipientState, clickIntents = uiState.clickIntents, + isBalanceHidden = uiState.isBalanceHidden, ) SendUiStateType.Fee -> SendSpeedAndFeeContent( state = uiState.feeState, diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/recipient/SendRecipientContent.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/recipient/SendRecipientContent.kt index 93910bc2a6..539d82e49c 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/recipient/SendRecipientContent.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/recipient/SendRecipientContent.kt @@ -18,6 +18,7 @@ import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.res.stringResource +import com.tangem.common.Strings.STARS import com.tangem.core.ui.components.inputrow.InputRowRecipient import com.tangem.core.ui.extensions.resolveReference import com.tangem.core.ui.res.TangemTheme @@ -25,6 +26,7 @@ import com.tangem.features.send.impl.R import com.tangem.features.send.impl.presentation.analytics.EnterAddressSource import com.tangem.features.send.impl.presentation.domain.SendRecipientListContent import com.tangem.features.send.impl.presentation.state.SendStates +import com.tangem.features.send.impl.presentation.state.fields.SendTextField import com.tangem.features.send.impl.presentation.ui.common.FooterContainer import com.tangem.features.send.impl.presentation.viewmodel.SendClickIntents import kotlinx.collections.immutable.ImmutableList @@ -33,7 +35,11 @@ private const val ADDRESS_FIELD_KEY = "ADDRESS_FIELD_KEY" private const val MEMO_FIELD_KEY = "MEMO_FIELD_KEY" @Composable -internal fun SendRecipientContent(uiState: SendStates.RecipientState?, clickIntents: SendClickIntents) { +internal fun SendRecipientContent( + uiState: SendStates.RecipientState?, + clickIntents: SendClickIntents, + isBalanceHidden: Boolean, +) { if (uiState == null) return val recipients = uiState.recent val wallets = uiState.wallets @@ -47,56 +53,89 @@ internal fun SendRecipientContent(uiState: SendStates.RecipientState?, clickInte .background(TangemTheme.colors.background.tertiary) .padding(horizontal = TangemTheme.dimens.spacing16), ) { - item(key = ADDRESS_FIELD_KEY) { - FooterContainer( - footer = stringResource(R.string.send_recipient_address_footer, uiState.network), - ) { - InputRowRecipient( - value = address.value, - title = address.label, - placeholder = address.placeholder, - onValueChange = address.onValueChange, - onPasteClick = { clickIntents.onRecipientAddressValueChange(it, EnterAddressSource.PasteButton) }, - isError = isError, - isLoading = isValidating, - error = address.error, - modifier = Modifier - .background( - color = TangemTheme.colors.background.action, - shape = TangemTheme.shapes.roundedCornersXMedium, - ), - ) - } - } - if (memoField != null) { - item(key = MEMO_FIELD_KEY) { - val placeholder = if (memoField.isEnabled) memoField.placeholder else memoField.disabledText - TextFieldWithPaste( - value = memoField.value, - label = memoField.label, - placeholder = placeholder, - footer = stringResource(R.string.send_recipient_memo_footer), - onValueChange = memoField.onValueChange, - onPasteClick = clickIntents::onRecipientMemoValueChange, - modifier = Modifier.padding(top = TangemTheme.dimens.spacing20), - isError = memoField.isError, - error = memoField.error, - isReadOnly = !memoField.isEnabled, - ) - } - } + addressItem( + address = address, + network = uiState.network, + isError = isError, + isValidating = isValidating, + onAddressChange = clickIntents::onRecipientAddressValueChange, + ) + memoField( + memoField = memoField, + onMemoChange = clickIntents::onRecipientMemoValueChange, + ) listHeaderItem( titleRes = R.string.send_recipient_wallets_title, isVisible = wallets.isNotEmpty() && wallets.first().isVisible, isFirst = true, ) - listItem(wallets, clickIntents, isLast = recipients.isEmpty()) + listItem( + list = wallets, + clickIntents = clickIntents, + isLast = recipients.isEmpty(), + isBalanceHidden = isBalanceHidden, + ) listHeaderItem( titleRes = R.string.send_recent_transactions, isVisible = recipients.isNotEmpty() && recipients.first().isVisible, isFirst = wallets.isEmpty(), ) - listItem(recipients, clickIntents, isLast = true) + listItem( + list = recipients, + clickIntents = clickIntents, + isLast = true, + isBalanceHidden = isBalanceHidden, + ) + } +} + +private fun LazyListScope.addressItem( + address: SendTextField.RecipientAddress, + network: String, + isError: Boolean, + isValidating: Boolean, + onAddressChange: (String, EnterAddressSource?) -> Unit, +) { + item(key = ADDRESS_FIELD_KEY) { + FooterContainer( + footer = stringResource(R.string.send_recipient_address_footer, network), + ) { + InputRowRecipient( + value = address.value, + title = address.label, + placeholder = address.placeholder, + onValueChange = address.onValueChange, + onPasteClick = { onAddressChange(it, EnterAddressSource.PasteButton) }, + isError = isError, + isLoading = isValidating, + error = address.error, + modifier = Modifier + .background( + color = TangemTheme.colors.background.action, + shape = TangemTheme.shapes.roundedCornersXMedium, + ), + ) + } + } +} + +private fun LazyListScope.memoField(memoField: SendTextField.RecipientMemo?, onMemoChange: (String) -> Unit) { + if (memoField != null) { + item(key = MEMO_FIELD_KEY) { + val placeholder = if (memoField.isEnabled) memoField.placeholder else memoField.disabledText + TextFieldWithPaste( + value = memoField.value, + label = memoField.label, + placeholder = placeholder, + footer = stringResource(R.string.send_recipient_memo_footer), + onValueChange = memoField.onValueChange, + onPasteClick = onMemoChange, + modifier = Modifier.padding(top = TangemTheme.dimens.spacing20), + isError = memoField.isError, + error = memoField.error, + isReadOnly = !memoField.isEnabled, + ) + } } } @@ -154,6 +193,7 @@ private fun LazyListScope.listItem( list: ImmutableList, clickIntents: SendClickIntents, isLast: Boolean, + isBalanceHidden: Boolean, ) { items( count = list.size, @@ -173,7 +213,7 @@ private fun LazyListScope.listItem( ) { ListItemWithIcon( title = title, - subtitle = item.subtitle.resolveReference(), + subtitle = if (isBalanceHidden) STARS else item.subtitle.resolveReference(), info = item.timestamp?.resolveReference(), subtitleEndOffset = item.subtitleEndOffset, subtitleIconRes = item.subtitleIconRes, From 046b64701a79f741808ecafb29d3789ac8559042 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 4 Apr 2024 15:14:22 +0500 Subject: [PATCH 11/67] Updated on 2026-08-14 --- .../presentation/QrScanningCameraDeniedBottomSheet.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/presentation/QrScanningCameraDeniedBottomSheet.kt b/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/presentation/QrScanningCameraDeniedBottomSheet.kt index 6edcbe2ea2..31f79da70e 100644 --- a/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/presentation/QrScanningCameraDeniedBottomSheet.kt +++ b/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/presentation/QrScanningCameraDeniedBottomSheet.kt @@ -33,7 +33,7 @@ private fun CameraDeniedBottomSheet(content: CameraDeniedBottomSheetConfig) { title = stringResource(id = R.string.qr_scanner_camera_denied_settings_button), icon = R.drawable.ic_settings_24, onItemsClick = { - val intent: Intent = Intent( + val intent = Intent( Settings.ACTION_APPLICATION_DETAILS_SETTINGS, Uri.fromParts("package", context.packageName, null), ) @@ -47,7 +47,7 @@ private fun CameraDeniedBottomSheet(content: CameraDeniedBottomSheetConfig) { ) SimpleSettingsRow( title = stringResource(id = R.string.common_close), - icon = R.drawable.ic_close, + icon = R.drawable.ic_close_24, onItemsClick = content.onCancelClick, modifier = Modifier.padding(bottom = TangemTheme.dimens.spacing16), ) From ed3ee24cec3706846288aac1d29090ff02197132 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 4 Apr 2024 15:15:18 +0500 Subject: [PATCH 12/67] Updated on 2026-08-14 --- .../send/impl/presentation/viewmodel/SendViewModel.kt | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendViewModel.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendViewModel.kt index 67e152dae9..010b405e81 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendViewModel.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendViewModel.kt @@ -618,7 +618,9 @@ internal class SendViewModel @Inject constructor( network = cryptoCurrency.network, address = value, ).getOrElse { false } - onEnteredValidAddress(isValidAddress) + val isAddressInWallet = cryptoCurrencyStatus.value.networkAddress?.availableAddresses + ?.any { it.value == value } ?: true + onEnteredValidAddress(isValidAddress, isAddressInWallet) return isValidAddress } @@ -632,12 +634,13 @@ internal class SendViewModel @Inject constructor( } ?: false } - private fun onEnteredValidAddress(isValidAddress: Boolean) { + private fun onEnteredValidAddress(isValidAddress: Boolean, isAddressInWallet: Boolean) { val recipientState = uiState.recipientState ?: return + val isVisible = isAddressInWallet || !isValidAddress uiState = uiState.copy( recipientState = recipientState.copy( - recent = recipientState.recent.map { it.copy(isVisible = !isValidAddress) }.toPersistentList(), - wallets = recipientState.wallets.map { it.copy(isVisible = !isValidAddress) }.toPersistentList(), + recent = recipientState.recent.map { it.copy(isVisible = isVisible) }.toPersistentList(), + wallets = recipientState.wallets.map { it.copy(isVisible = isVisible) }.toPersistentList(), ), ) } From 4bd283457bdbd980c96a54d601b9e110f1cbccd1 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 4 Apr 2024 15:15:39 +0500 Subject: [PATCH 13/67] Updated on 2026-08-14 --- .../ui/recipient/TextFieldWithPaste.kt | 25 +++++++++++++++---- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/recipient/TextFieldWithPaste.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/recipient/TextFieldWithPaste.kt index 6109c5ff50..5c0ca33a84 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/recipient/TextFieldWithPaste.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/recipient/TextFieldWithPaste.kt @@ -1,5 +1,8 @@ package com.tangem.features.send.impl.presentation.ui.recipient +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut import androidx.compose.foundation.background import androidx.compose.foundation.layout.* import androidx.compose.material3.Text @@ -63,17 +66,29 @@ internal fun TextFieldWithPaste( .padding(top = TangemTheme.dimens.spacing6), ) } - CrossIcon( - onClick = onPasteClick, + AnimatedVisibility( + visible = !isReadOnly, + label = "Animate read only status change", + enter = fadeIn(), + exit = fadeOut(), modifier = Modifier .align(CenterVertically), - ) + ) { + CrossIcon( + onClick = onPasteClick, + ) + } } - if (!isReadOnly) { + AnimatedVisibility( + visible = !isReadOnly, + label = "Animate read only status change", + enter = fadeIn(), + exit = fadeOut(), + modifier = Modifier.align(CenterEnd), + ) { PasteButton( isPasteButtonVisible = value.isBlank(), onClick = onPasteClick, - modifier = Modifier.align(CenterEnd), ) } } From 2c56bff66bcf11381cef7728b4cd07119e11cf96 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 4 Apr 2024 14:01:15 +0100 Subject: [PATCH 14/67] Updated on 2026-08-14 --- .../BiometricUserWalletsListManager.kt | 4 ++++ .../GeneralUserWalletsListManager.kt | 4 ++++ .../RuntimeUserWalletsListManager.kt | 4 ++++ .../saveWallet/redux/SaveWalletMiddleware.kt | 3 +-- .../wallets/legacy/UserWalletsListManager.kt | 5 +++++ .../UserWalletsListManagerExtensions.kt | 21 ++++--------------- 6 files changed, 22 insertions(+), 19 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManager.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManager.kt index 160d433012..99418a7aa0 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManager.kt @@ -81,6 +81,10 @@ internal class BiometricUserWalletsListManager( state.update { State() } } + override fun isLockable(): Boolean { + return true + } + override suspend fun select(userWalletId: UserWalletId): CompletionResult = catching { if (state.value.selectedUserWalletId == userWalletId) { return@catching findSelectedUserWallet()!! diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/GeneralUserWalletsListManager.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/GeneralUserWalletsListManager.kt index 1e01c26cd2..762ba82f54 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/GeneralUserWalletsListManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/GeneralUserWalletsListManager.kt @@ -118,6 +118,10 @@ internal class GeneralUserWalletsListManager( } } + override fun isLockable(): Boolean { + return implementation.value.isLockable() + } + private fun subscribeOnCurrentManager() { appPreferencesStore.get(key = PreferencesKeys.SAVE_USER_WALLETS_KEY, default = false) .distinctUntilChanged() diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/RuntimeUserWalletsListManager.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/RuntimeUserWalletsListManager.kt index eedc68771b..31ef3366dd 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/RuntimeUserWalletsListManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/RuntimeUserWalletsListManager.kt @@ -85,6 +85,10 @@ internal class RuntimeUserWalletsListManager : UserWalletsListManager { state.value.userWallet ?: walletNotFound() } + override fun isLockable(): Boolean { + return false + } + private fun saveInternal(userWallet: UserWallet): CompletionResult = catching { state.update { prevState -> prevState.copy( diff --git a/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletMiddleware.kt b/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletMiddleware.kt index 62fa03d837..9e327ce3e5 100644 --- a/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletMiddleware.kt @@ -8,7 +8,6 @@ import com.tangem.core.navigation.AppScreen import com.tangem.core.navigation.NavigationAction import com.tangem.domain.userwallets.UserWalletBuilder import com.tangem.domain.wallets.legacy.UserWalletsListManager -import com.tangem.domain.wallets.legacy.isLockable import com.tangem.domain.wallets.models.UserWallet import com.tangem.tap.* import com.tangem.tap.common.analytics.events.AnalyticsParam @@ -208,7 +207,7 @@ internal class SaveWalletMiddleware { } private suspend fun provideLockableUserWalletsListManagerIfNot() { - if (store.state.globalState.userWalletsListManager?.isLockable == true) return + if (store.state.globalState.userWalletsListManager?.isLockable() == true) return val context = foregroundActivityObserver.foregroundActivity?.applicationContext.guard { val error = IllegalStateException("No activities in foreground") diff --git a/domain/wallets/src/main/java/com/tangem/domain/wallets/legacy/UserWalletsListManager.kt b/domain/wallets/src/main/java/com/tangem/domain/wallets/legacy/UserWalletsListManager.kt index 8a63233bed..213d780d07 100644 --- a/domain/wallets/src/main/java/com/tangem/domain/wallets/legacy/UserWalletsListManager.kt +++ b/domain/wallets/src/main/java/com/tangem/domain/wallets/legacy/UserWalletsListManager.kt @@ -84,6 +84,11 @@ interface UserWalletsListManager { */ suspend fun get(userWalletId: UserWalletId): CompletionResult + /** + * Indicates that the [UserWalletsListManager] supports [UserWalletsListManager.Lockable] + * */ + fun isLockable(): Boolean + interface Lockable : UserWalletsListManager { /** diff --git a/domain/wallets/src/main/java/com/tangem/domain/wallets/legacy/UserWalletsListManagerExtensions.kt b/domain/wallets/src/main/java/com/tangem/domain/wallets/legacy/UserWalletsListManagerExtensions.kt index 0003a1300d..a6bdbde07f 100644 --- a/domain/wallets/src/main/java/com/tangem/domain/wallets/legacy/UserWalletsListManagerExtensions.kt +++ b/domain/wallets/src/main/java/com/tangem/domain/wallets/legacy/UserWalletsListManagerExtensions.kt @@ -6,12 +6,6 @@ import com.tangem.domain.wallets.models.UserWallet import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flowOf -/** - * Indicates that the [UserWalletsListManager] implements [UserWalletsListManager.Lockable] - * */ -val UserWalletsListManager.isLockable: Boolean - get() = this is UserWalletsListManager.Lockable - /** * Indicates that the [UserWalletsListManager] is locked * @@ -48,16 +42,6 @@ suspend fun UserWalletsListManager.unlockIfLockable(type: UnlockType = UnlockTyp return asLockable()?.unlock(type) ?: CompletionResult.Failure(UserWalletsListError.UnableToUnlockUserWallets()) } -/** - * Call [UserWalletsListManager.Lockable.lock] if [UserWalletsListManager] implements [UserWalletsListManager.Lockable] - * or do nothing otherwise - * - * @see UserWalletsListManager.Lockable.lock - * */ -fun UserWalletsListManager.lockIfLockable() { - asLockable()?.lock() -} - /** * Safe cast [UserWalletsListManager] to [UserWalletsListManager.Lockable] * @@ -65,5 +49,8 @@ fun UserWalletsListManager.lockIfLockable() { * [UserWalletsListManager.Lockable] otherwise * */ fun UserWalletsListManager.asLockable(): UserWalletsListManager.Lockable? { - return this as? UserWalletsListManager.Lockable + if (this.isLockable()) { + return this as? UserWalletsListManager.Lockable + } + return null } \ No newline at end of file From decd081f950091b887fa3a04550c3d1ce2ff152a Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 4 Apr 2024 14:37:10 +0100 Subject: [PATCH 15/67] Updated on 2026-08-14 --- .../BiometricUserWalletsListManager.kt | 4 ++++ .../GeneralUserWalletsListManager.kt | 4 ++++ .../RuntimeUserWalletsListManager.kt | 4 ++++ .../saveWallet/redux/SaveWalletMiddleware.kt | 3 +-- .../wallets/legacy/UserWalletsListManager.kt | 5 +++++ .../UserWalletsListManagerExtensions.kt | 21 ++++--------------- 6 files changed, 22 insertions(+), 19 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManager.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManager.kt index 160d433012..99418a7aa0 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManager.kt @@ -81,6 +81,10 @@ internal class BiometricUserWalletsListManager( state.update { State() } } + override fun isLockable(): Boolean { + return true + } + override suspend fun select(userWalletId: UserWalletId): CompletionResult = catching { if (state.value.selectedUserWalletId == userWalletId) { return@catching findSelectedUserWallet()!! diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/GeneralUserWalletsListManager.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/GeneralUserWalletsListManager.kt index 1e01c26cd2..762ba82f54 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/GeneralUserWalletsListManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/GeneralUserWalletsListManager.kt @@ -118,6 +118,10 @@ internal class GeneralUserWalletsListManager( } } + override fun isLockable(): Boolean { + return implementation.value.isLockable() + } + private fun subscribeOnCurrentManager() { appPreferencesStore.get(key = PreferencesKeys.SAVE_USER_WALLETS_KEY, default = false) .distinctUntilChanged() diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/RuntimeUserWalletsListManager.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/RuntimeUserWalletsListManager.kt index eedc68771b..31ef3366dd 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/RuntimeUserWalletsListManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/RuntimeUserWalletsListManager.kt @@ -85,6 +85,10 @@ internal class RuntimeUserWalletsListManager : UserWalletsListManager { state.value.userWallet ?: walletNotFound() } + override fun isLockable(): Boolean { + return false + } + private fun saveInternal(userWallet: UserWallet): CompletionResult = catching { state.update { prevState -> prevState.copy( diff --git a/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletMiddleware.kt b/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletMiddleware.kt index 62fa03d837..9e327ce3e5 100644 --- a/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletMiddleware.kt @@ -8,7 +8,6 @@ import com.tangem.core.navigation.AppScreen import com.tangem.core.navigation.NavigationAction import com.tangem.domain.userwallets.UserWalletBuilder import com.tangem.domain.wallets.legacy.UserWalletsListManager -import com.tangem.domain.wallets.legacy.isLockable import com.tangem.domain.wallets.models.UserWallet import com.tangem.tap.* import com.tangem.tap.common.analytics.events.AnalyticsParam @@ -208,7 +207,7 @@ internal class SaveWalletMiddleware { } private suspend fun provideLockableUserWalletsListManagerIfNot() { - if (store.state.globalState.userWalletsListManager?.isLockable == true) return + if (store.state.globalState.userWalletsListManager?.isLockable() == true) return val context = foregroundActivityObserver.foregroundActivity?.applicationContext.guard { val error = IllegalStateException("No activities in foreground") diff --git a/domain/wallets/src/main/java/com/tangem/domain/wallets/legacy/UserWalletsListManager.kt b/domain/wallets/src/main/java/com/tangem/domain/wallets/legacy/UserWalletsListManager.kt index 8a63233bed..213d780d07 100644 --- a/domain/wallets/src/main/java/com/tangem/domain/wallets/legacy/UserWalletsListManager.kt +++ b/domain/wallets/src/main/java/com/tangem/domain/wallets/legacy/UserWalletsListManager.kt @@ -84,6 +84,11 @@ interface UserWalletsListManager { */ suspend fun get(userWalletId: UserWalletId): CompletionResult + /** + * Indicates that the [UserWalletsListManager] supports [UserWalletsListManager.Lockable] + * */ + fun isLockable(): Boolean + interface Lockable : UserWalletsListManager { /** diff --git a/domain/wallets/src/main/java/com/tangem/domain/wallets/legacy/UserWalletsListManagerExtensions.kt b/domain/wallets/src/main/java/com/tangem/domain/wallets/legacy/UserWalletsListManagerExtensions.kt index 0003a1300d..a6bdbde07f 100644 --- a/domain/wallets/src/main/java/com/tangem/domain/wallets/legacy/UserWalletsListManagerExtensions.kt +++ b/domain/wallets/src/main/java/com/tangem/domain/wallets/legacy/UserWalletsListManagerExtensions.kt @@ -6,12 +6,6 @@ import com.tangem.domain.wallets.models.UserWallet import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flowOf -/** - * Indicates that the [UserWalletsListManager] implements [UserWalletsListManager.Lockable] - * */ -val UserWalletsListManager.isLockable: Boolean - get() = this is UserWalletsListManager.Lockable - /** * Indicates that the [UserWalletsListManager] is locked * @@ -48,16 +42,6 @@ suspend fun UserWalletsListManager.unlockIfLockable(type: UnlockType = UnlockTyp return asLockable()?.unlock(type) ?: CompletionResult.Failure(UserWalletsListError.UnableToUnlockUserWallets()) } -/** - * Call [UserWalletsListManager.Lockable.lock] if [UserWalletsListManager] implements [UserWalletsListManager.Lockable] - * or do nothing otherwise - * - * @see UserWalletsListManager.Lockable.lock - * */ -fun UserWalletsListManager.lockIfLockable() { - asLockable()?.lock() -} - /** * Safe cast [UserWalletsListManager] to [UserWalletsListManager.Lockable] * @@ -65,5 +49,8 @@ fun UserWalletsListManager.lockIfLockable() { * [UserWalletsListManager.Lockable] otherwise * */ fun UserWalletsListManager.asLockable(): UserWalletsListManager.Lockable? { - return this as? UserWalletsListManager.Lockable + if (this.isLockable()) { + return this as? UserWalletsListManager.Lockable + } + return null } \ No newline at end of file From 1b9352be36f7aff15ee35305cecfa33a6574894c Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 4 Apr 2024 15:18:26 +0100 Subject: [PATCH 16/67] Updated on 2026-08-14 --- gradle/dependencies.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/dependencies.toml b/gradle/dependencies.toml index 5718b48918..555b8214d4 100644 --- a/gradle/dependencies.toml +++ b/gradle/dependencies.toml @@ -85,7 +85,7 @@ web3j = "4.10.1" # endregion Other libraries # region Tangem -tangemBlockchainSdk = "release-app_5.8-562" +tangemBlockchainSdk = "release-app_5.8-573" #tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds tangemCardSdk = "release-app_5.8-341" #tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^ From f99f050d237d1e8ee37486f099419cf18f1adfb6 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 5 Apr 2024 23:26:01 +0100 Subject: [PATCH 17/67] Updated on 2026-08-14 --- gradle/dependencies.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/dependencies.toml b/gradle/dependencies.toml index 555b8214d4..b85ea5b9c1 100644 --- a/gradle/dependencies.toml +++ b/gradle/dependencies.toml @@ -85,7 +85,7 @@ web3j = "4.10.1" # endregion Other libraries # region Tangem -tangemBlockchainSdk = "release-app_5.8-573" +tangemBlockchainSdk = "release-app_5.8-578" #tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds tangemCardSdk = "release-app_5.8-341" #tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^ From 0b32fcb92580bee99ea7d657927e524246ddd7a7 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 8 Apr 2024 11:30:31 +0500 Subject: [PATCH 18/67] Updated on 2026-08-14 --- .../send/impl/presentation/state/SendUiState.kt | 1 + .../presentation/state/fee/FeeStateFactory.kt | 16 +++++++++++++--- .../state/fee/SendFeeStateConverter.kt | 1 + .../state/previewdata/FeeStatePreviewData.kt | 1 + 4 files changed, 16 insertions(+), 3 deletions(-) diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendUiState.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendUiState.kt index a4306da4f4..c8e1d05950 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendUiState.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendUiState.kt @@ -77,6 +77,7 @@ internal sealed class SendStates { val rate: BigDecimal?, val appCurrency: AppCurrency, val isFeeApproximate: Boolean, + val isCustomSelected: Boolean, val notifications: ImmutableList, ) : SendStates() diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeStateFactory.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeStateFactory.kt index 997c39d2f1..5ef07bb664 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeStateFactory.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeStateFactory.kt @@ -56,18 +56,26 @@ internal class FeeStateFactory( fun onFeeOnLoadedState(fees: TransactionFee): SendUiState { val state = currentStateProvider() val feeState = state.feeState ?: return state - val feeSelectorState = (feeState.feeSelectorState as? FeeSelectorState.Content)?.copy( + val feeSelectorState = feeState.feeSelectorState as? FeeSelectorState.Content + + val isCustomWasSelected = if (feeState.isCustomSelected) { + feeSelectorState?.customValues ?: persistentListOf() + } else { + customFeeFieldConverter.convert(fees.normal) + } + val updatedFeeSelectorState = feeSelectorState?.copy( fees = fees, + customValues = isCustomWasSelected, ) ?: FeeSelectorState.Content( fees = fees, customValues = customFeeFieldConverter.convert(fees.normal), ) - val fee = feeConverter.convert(feeSelectorState) + val fee = feeConverter.convert(updatedFeeSelectorState) return state.copy( amountState = state.amountState?.copy(isFeeLoading = false), feeState = feeState.copy( - feeSelectorState = feeSelectorState, + feeSelectorState = updatedFeeSelectorState, fee = fee, isFeeApproximate = isFeeApproximate(fee), ), @@ -91,9 +99,11 @@ internal class FeeStateFactory( val updatedFeeSelectorState = feeSelectorState.copy(selectedFee = feeType) val fee = feeConverter.convert(updatedFeeSelectorState) + val isCustomFeeWasSelected = feeState.isCustomSelected || updatedFeeSelectorState.selectedFee == FeeType.Custom return state.copy( feeState = feeState.copy( fee = fee, + isCustomSelected = isCustomFeeWasSelected, feeSelectorState = updatedFeeSelectorState, ), ) diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/SendFeeStateConverter.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/SendFeeStateConverter.kt index caf0ee5683..8bab03a2c8 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/SendFeeStateConverter.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/SendFeeStateConverter.kt @@ -20,6 +20,7 @@ internal class SendFeeStateConverter( rate = feeCryptoCurrencyStatusProvider().value.fiatRate, appCurrency = appCurrencyProvider(), isFeeApproximate = false, + isCustomSelected = false, ) } } \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/previewdata/FeeStatePreviewData.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/previewdata/FeeStatePreviewData.kt index c07c45a39b..766380d826 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/previewdata/FeeStatePreviewData.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/previewdata/FeeStatePreviewData.kt @@ -48,6 +48,7 @@ internal object FeeStatePreviewData { ), isFeeApproximate = false, notifications = persistentListOf(), + isCustomSelected = false, ) val errorFeeState = feeState.copy( From 38d95a7a9cbc82b346dc1a45a80375745944b0bd Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 8 Apr 2024 11:33:39 +0500 Subject: [PATCH 19/67] Updated on 2026-08-14 --- .../AmountVisualTransformation.kt | 12 ++--- .../core/ui/utils/BigDecimalFormatter.kt | 48 +++++++++---------- .../fields/SendAmountFieldChangeConverter.kt | 9 +++- .../impl/presentation/ui/send/AmountBlock.kt | 10 ++-- 4 files changed, 38 insertions(+), 41 deletions(-) diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/fields/visualtransformations/AmountVisualTransformation.kt b/core/ui/src/main/java/com/tangem/core/ui/components/fields/visualtransformations/AmountVisualTransformation.kt index 5ef8a2c4e3..8f10352030 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/fields/visualtransformations/AmountVisualTransformation.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/fields/visualtransformations/AmountVisualTransformation.kt @@ -7,7 +7,6 @@ import androidx.compose.ui.text.input.VisualTransformation import com.tangem.core.ui.utils.BigDecimalFormatter import com.tangem.core.ui.utils.defaultFormat import com.tangem.core.ui.utils.formatWithThousands -import com.tangem.core.ui.utils.parseToBigDecimal import java.text.DecimalFormat class AmountVisualTransformation( @@ -23,21 +22,16 @@ class AmountVisualTransformation( decimals, ) formattedAmount = formattedAmount.ifEmpty { decimalFormat.defaultFormat() } - val decimalValue = text.text.parseToBigDecimal(decimals) val formattedText = if (formattedAmount.isNotEmpty() && symbol != null) { AnnotatedString( if (currencyCode != null) { - BigDecimalFormatter.formatFiatAmount( - fiatAmount = decimalValue, + BigDecimalFormatter.formatFiatEditableAmount( + fiatAmount = formattedAmount, fiatCurrencyCode = currencyCode, fiatCurrencySymbol = symbol, ) } else { - BigDecimalFormatter.formatCryptoAmountUncapped( - cryptoAmount = decimalValue, - cryptoSymbol = symbol, - decimals = decimals, - ) + BigDecimalFormatter.formatWithSymbol(formattedAmount, symbol) }, ) } else { diff --git a/core/ui/src/main/java/com/tangem/core/ui/utils/BigDecimalFormatter.kt b/core/ui/src/main/java/com/tangem/core/ui/utils/BigDecimalFormatter.kt index 3ca0b57adf..42309223b1 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/utils/BigDecimalFormatter.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/utils/BigDecimalFormatter.kt @@ -1,8 +1,10 @@ package com.tangem.core.ui.utils import com.tangem.domain.tokens.model.CryptoCurrency +import timber.log.Timber import java.math.BigDecimal import java.math.RoundingMode +import java.text.DecimalFormat import java.text.NumberFormat import java.util.Currency import java.util.Locale @@ -38,30 +40,6 @@ object BigDecimalFormatter { } } - fun formatCryptoAmountUncapped( - cryptoAmount: BigDecimal?, - cryptoSymbol: String, - decimals: Int, - locale: Locale = Locale.getDefault(), - ): String { - if (cryptoAmount == null) return EMPTY_BALANCE_SIGN - - val formatter = NumberFormat.getNumberInstance(locale).apply { - maximumFractionDigits = decimals - minimumFractionDigits = minOf(2, decimals) - isGroupingUsed = true - roundingMode = RoundingMode.DOWN - } - - return formatter.format(cryptoAmount).let { - if (cryptoSymbol.isEmpty()) { - it - } else { - it + "\u2009$cryptoSymbol" - } - } - } - fun formatCryptoAmount( cryptoAmount: BigDecimal?, cryptoCurrency: CryptoCurrency, @@ -90,6 +68,26 @@ object BigDecimalFormatter { .replace(formatterCurrency.getSymbol(locale), fiatCurrencySymbol) } + fun formatFiatEditableAmount( + fiatAmount: String?, + fiatCurrencyCode: String, + fiatCurrencySymbol: String, + locale: Locale = Locale.getDefault(), + ): String { + if (fiatAmount == null) return EMPTY_BALANCE_SIGN + + val formatterCurrency = getCurrency(fiatCurrencyCode) + val numberFormatter = NumberFormat.getCurrencyInstance(locale).apply { + currency = formatterCurrency + } + val formatter = requireNotNull(numberFormatter as? DecimalFormat) { + Timber.e("NumberFormat is null") + return EMPTY_BALANCE_SIGN + } + return "${formatter.positivePrefix}$fiatAmount${formatter.positiveSuffix}" + .replace(formatterCurrency.getSymbol(locale), fiatCurrencySymbol) + } + fun formatPercent( percent: BigDecimal, useAbsoluteValue: Boolean, @@ -107,6 +105,8 @@ object BigDecimalFormatter { return formatter.format(value) } + fun formatWithSymbol(amount: String, symbol: String) = "$amount\u2009$symbol" + private fun getCurrency(code: String): Currency { return runCatching { Currency.getInstance(code) } .getOrElse { e -> diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fields/SendAmountFieldChangeConverter.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fields/SendAmountFieldChangeConverter.kt index 273e457783..a1fc8fbe25 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fields/SendAmountFieldChangeConverter.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fields/SendAmountFieldChangeConverter.kt @@ -45,7 +45,7 @@ internal class SendAmountFieldChangeConverter( cryptoAmount = amountTextField.cryptoAmount.copy(value = decimalCryptoValue), fiatAmount = amountTextField.fiatAmount.copy(value = decimalFiatValue), keyboardOptions = KeyboardOptions( - imeAction = if (!isExceedBalance) ImeAction.Done else ImeAction.None, + imeAction = getKeyboardAction(isExceedBalance, decimalCryptoValue), keyboardType = KeyboardType.Number, ), ), @@ -103,4 +103,11 @@ internal class SendAmountFieldChangeConverter( cryptoDecimal > currencyCryptoAmount } } + + private fun getKeyboardAction(isExceedBalance: Boolean, decimalCryptoValue: BigDecimal) = + if (!isExceedBalance && !decimalCryptoValue.isZero()) { + ImeAction.Done + } else { + ImeAction.None + } } \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/send/AmountBlock.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/send/AmountBlock.kt index 528cdfea00..85ff055ed6 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/send/AmountBlock.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/send/AmountBlock.kt @@ -30,13 +30,9 @@ internal fun AmountBlock( ) { val amount = amountState.amountTextField - val cryptoAmount = BigDecimalFormatter.formatCryptoAmount( - cryptoAmount = amount.cryptoAmount.value, - cryptoCurrency = amount.cryptoAmount.currencySymbol, - decimals = amount.cryptoAmount.decimals, - ) - val fiatAmount = BigDecimalFormatter.formatFiatAmount( - fiatAmount = amount.fiatAmount.value, + val cryptoAmount = BigDecimalFormatter.formatWithSymbol(amount.value, amount.cryptoAmount.currencySymbol) + val fiatAmount = BigDecimalFormatter.formatFiatEditableAmount( + fiatAmount = amount.fiatValue, fiatCurrencySymbol = amount.fiatAmount.currencySymbol, fiatCurrencyCode = amountState.appCurrencyCode, ) From df7e72f5275fa8d8cad4e47408b398a3a34bf561 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 8 Apr 2024 13:40:33 +0500 Subject: [PATCH 20/67] Updated on 2026-08-14 --- .../presentation/state/SendStateFactory.kt | 2 +- .../presentation/state/fee/FeeConverter.kt | 4 ++-- .../presentation/state/fee/FeeStateFactory.kt | 4 ++-- .../state/fee/SendFeeCustomFieldConverter.kt | 4 ++-- .../state/fee/SendFeeStateConverter.kt | 4 ++-- .../fee/custom/EthereumCustomFeeConverter.kt | 23 +++++++++--------- .../presentation/ui/SendNavigationButtons.kt | 2 +- .../presentation/ui/send/RecipientBlock.kt | 24 ------------------- .../presentation/viewmodel/SendViewModel.kt | 8 +++---- 9 files changed, 26 insertions(+), 49 deletions(-) diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendStateFactory.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendStateFactory.kt index f50a061117..7a5c113219 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendStateFactory.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendStateFactory.kt @@ -33,7 +33,7 @@ internal class SendStateFactory( private val userWalletProvider: Provider, private val appCurrencyProvider: Provider, private val cryptoCurrencyStatusProvider: Provider, - private val feeCryptoCurrencyStatusProvider: Provider, + private val feeCryptoCurrencyStatusProvider: Provider, private val isTapHelpPreviewEnabledProvider: Provider, private val validateWalletMemoUseCase: ValidateWalletMemoUseCase, private val getExplorerTransactionUrlUseCase: GetExplorerTransactionUrlUseCase, diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeConverter.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeConverter.kt index bd8e832a91..2d876ae4e9 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeConverter.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeConverter.kt @@ -13,10 +13,10 @@ import com.tangem.utils.converter.Converter internal class FeeConverter( private val clickIntents: SendClickIntents, private val appCurrencyProvider: Provider, - private val feeCryptoCurrencyStatusProvider: Provider, + private val feeCryptoCurrencyStatusProvider: Provider, ) : Converter { - private val ethereumCustomFeeConverter by lazy { + private val ethereumCustomFeeConverter by lazy(LazyThreadSafetyMode.NONE) { EthereumCustomFeeConverter( clickIntents = clickIntents, appCurrencyProvider = appCurrencyProvider, diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeStateFactory.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeStateFactory.kt index 5ef07bb664..ba181f423f 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeStateFactory.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeStateFactory.kt @@ -21,7 +21,7 @@ import kotlinx.collections.immutable.persistentListOf internal class FeeStateFactory( private val clickIntents: SendClickIntents, private val currentStateProvider: Provider, - private val feeCryptoCurrencyStatusProvider: Provider, + private val feeCryptoCurrencyStatusProvider: Provider, private val appCurrencyProvider: Provider, private val isFeeApproximateUseCase: IsFeeApproximateUseCase, ) { @@ -153,7 +153,7 @@ internal class FeeStateFactory( } private fun isFeeApproximate(fee: Fee): Boolean { - val cryptoCurrencyStatus = feeCryptoCurrencyStatusProvider() + val cryptoCurrencyStatus = feeCryptoCurrencyStatusProvider() ?: return false return isFeeApproximateUseCase( networkId = cryptoCurrencyStatus.currency.network.id, amountType = fee.amount.type, diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/SendFeeCustomFieldConverter.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/SendFeeCustomFieldConverter.kt index cdd0fa60e6..79c7a7e318 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/SendFeeCustomFieldConverter.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/SendFeeCustomFieldConverter.kt @@ -14,10 +14,10 @@ import kotlinx.collections.immutable.persistentListOf internal class SendFeeCustomFieldConverter( private val clickIntents: SendClickIntents, private val appCurrencyProvider: Provider, - private val feeCryptoCurrencyStatusProvider: Provider, + private val feeCryptoCurrencyStatusProvider: Provider, ) : Converter> { - private val ethereumCustomFeeConverter by lazy { + private val ethereumCustomFeeConverter by lazy(LazyThreadSafetyMode.NONE) { EthereumCustomFeeConverter( clickIntents = clickIntents, appCurrencyProvider = appCurrencyProvider, diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/SendFeeStateConverter.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/SendFeeStateConverter.kt index 8bab03a2c8..d9b92b387d 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/SendFeeStateConverter.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/SendFeeStateConverter.kt @@ -9,7 +9,7 @@ import kotlinx.collections.immutable.persistentListOf internal class SendFeeStateConverter( private val appCurrencyProvider: Provider, - private val feeCryptoCurrencyStatusProvider: Provider, + private val feeCryptoCurrencyStatusProvider: Provider, ) : Converter { override fun convert(value: Unit): SendStates.FeeState { @@ -17,7 +17,7 @@ internal class SendFeeStateConverter( feeSelectorState = FeeSelectorState.Error, fee = null, notifications = persistentListOf(), - rate = feeCryptoCurrencyStatusProvider().value.fiatRate, + rate = feeCryptoCurrencyStatusProvider()?.value?.fiatRate, appCurrency = appCurrencyProvider(), isFeeApproximate = false, isCustomSelected = false, diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/custom/EthereumCustomFeeConverter.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/custom/EthereumCustomFeeConverter.kt index 11dfc8f056..97726412f7 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/custom/EthereumCustomFeeConverter.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/custom/EthereumCustomFeeConverter.kt @@ -28,7 +28,7 @@ import java.math.RoundingMode internal class EthereumCustomFeeConverter( private val clickIntents: SendClickIntents, private val appCurrencyProvider: Provider, - private val feeCryptoCurrencyStatusProvider: Provider, + private val feeCryptoCurrencyStatusProvider: Provider, ) : Converter> { override fun convert(value: Fee.Ethereum): ImmutableList { @@ -49,8 +49,8 @@ internal class EthereumCustomFeeConverter( keyboardActions = KeyboardActions(), ), SendTextField.CustomFee( - value = value.gasPrice.toString(), - decimals = GAS_DECIMALS, + value = value.gasPrice.toBigDecimal().movePointLeft(GIGA_DECIMALS).toString(), + decimals = GIGA_DECIMALS, symbol = ETHEREUM_GAS_UNIT, title = resourceReference(R.string.send_gas_price), footer = resourceReference(R.string.send_gas_price_footer), @@ -105,7 +105,7 @@ internal class EthereumCustomFeeConverter( private fun getFeeFormatted(fee: BigDecimal?): TextReference { val appCurrency = appCurrencyProvider() - val rate = feeCryptoCurrencyStatusProvider().value.fiatRate + val rate = feeCryptoCurrencyStatusProvider()?.value?.fiatRate val fiatFee = rate?.let { fee?.multiply(it) } return stringReference( BigDecimalFormatter.formatFiatAmount( @@ -118,7 +118,7 @@ internal class EthereumCustomFeeConverter( private fun checkExceedBalance(feeAmount: BigDecimal?): Boolean { val cryptoCurrencyStatus = feeCryptoCurrencyStatusProvider() - val currencyCryptoAmount = cryptoCurrencyStatus.value.amount ?: BigDecimal.ZERO + val currencyCryptoAmount = cryptoCurrencyStatus?.value?.amount ?: BigDecimal.ZERO return feeAmount == null || feeAmount.isZero() || feeAmount > currencyCryptoAmount } @@ -134,9 +134,9 @@ internal class EthereumCustomFeeConverter( setEmpty(GAS_PRICE) } else { val newFeeAmountDecimal = value.parseToBigDecimal(this[FEE_AMOUNT].decimals) - val newFeeAmount = newFeeAmountDecimal.movePointRight(this[FEE_AMOUNT].decimals) - val newGasPrice = newFeeAmount.divide(gasLimit, GAS_DECIMALS, RoundingMode.HALF_UP) - set(GAS_PRICE, this[GAS_PRICE].copy(value = newGasPrice.parseBigDecimal(GAS_DECIMALS))) + val newFeeAmount = newFeeAmountDecimal.movePointRight(this[GAS_PRICE].decimals) // from ETH to GWEI + val newGasPrice = newFeeAmount.divide(gasLimit, this[GAS_PRICE].decimals, RoundingMode.HALF_UP) + set(GAS_PRICE, this[GAS_PRICE].copy(value = newGasPrice.parseBigDecimal(this[GAS_PRICE].decimals))) set( index, this[index].copy( @@ -154,8 +154,8 @@ internal class EthereumCustomFeeConverter( setEmpty(GAS_PRICE) } else { val newGasPrice = value.parseToBigDecimal(this[GAS_PRICE].decimals) - .movePointLeft(this[GAS_PRICE].decimals) - val newFeeAmount = (gasLimit * newGasPrice).movePointLeft(this[FEE_AMOUNT].decimals) + .movePointLeft(this[GAS_PRICE].decimals) // from GWEI to ETH + val newFeeAmount = gasLimit * newGasPrice set( FEE_AMOUNT, this[FEE_AMOUNT].copy( @@ -174,7 +174,7 @@ internal class EthereumCustomFeeConverter( } else { val newGasLimit = value.parseToBigDecimal(this[GAS_LIMIT].decimals) val gasPrice = this[GAS_PRICE].value.parseToBigDecimal(this[GAS_PRICE].decimals) - .movePointLeft(this[FEE_AMOUNT].decimals) + .movePointLeft(this[GAS_PRICE].decimals) // from GWEI to ETH val newFeeAmount = newGasLimit * gasPrice set( FEE_AMOUNT, @@ -198,6 +198,7 @@ internal class EthereumCustomFeeConverter( companion object { private const val ETHEREUM_GAS_UNIT = "GWEI" + private const val GIGA_DECIMALS = 9 private const val FEE_AMOUNT = 0 private const val GAS_PRICE = 1 private const val GAS_LIMIT = 2 diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/SendNavigationButtons.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/SendNavigationButtons.kt index 5bdb10824e..20f2800793 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/SendNavigationButtons.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/SendNavigationButtons.kt @@ -140,7 +140,7 @@ private fun SendingText(uiState: SendUiState, isVisible: Boolean, modifier: Modi val feeState = uiState.feeState val fiatRate = feeState?.rate val fiatAmount = amountState?.amountTextField?.fiatAmount - val feeFiat = feeState?.fee?.amount?.value?.multiply(fiatRate) + val feeFiat = fiatRate?.let { feeState.fee?.amount?.value?.multiply(it) } val sendingFiat = feeFiat?.let { fiatAmount?.value?.plus(it) } if (feeFiat != null && sendingFiat != null) { diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/send/RecipientBlock.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/send/RecipientBlock.kt index 473d043d35..f276edf3c1 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/send/RecipientBlock.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/send/RecipientBlock.kt @@ -4,7 +4,6 @@ import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment @@ -41,7 +40,6 @@ internal fun RecipientBlock( .padding(TangemTheme.dimens.spacing12), ) { AddressBlock(recipientState.addressTextField) - MemoBlock(recipientState.memoTextField) } } @@ -72,28 +70,6 @@ private fun AddressBlock(address: SendTextField.RecipientAddress) { } } -@Composable -private fun MemoBlock(memo: SendTextField.RecipientMemo?) { - val showMemo = memo != null && memo.value.isNotBlank() - if (showMemo) { - HorizontalDivider( - color = TangemTheme.colors.stroke.primary, - modifier = Modifier.padding(vertical = TangemTheme.dimens.spacing12), - ) - Text( - text = memo?.label?.resolveReference().orEmpty(), - style = TangemTheme.typography.caption2, - color = TangemTheme.colors.text.secondary, - ) - Text( - text = memo?.value.orEmpty(), - style = TangemTheme.typography.body2, - color = TangemTheme.colors.text.primary1, - modifier = Modifier.padding(top = TangemTheme.dimens.spacing8), - ) - } -} - // region Preview @Preview @Composable diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendViewModel.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendViewModel.kt index 8ea9eebab5..df9aa391e9 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendViewModel.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendViewModel.kt @@ -184,7 +184,7 @@ internal class SendViewModel @Inject constructor( private var isTapHelpPreviewEnabled: Boolean = false private var coinCryptoCurrencyStatus: CryptoCurrencyStatus by Delegates.notNull() private var cryptoCurrencyStatus: CryptoCurrencyStatus by Delegates.notNull() - private var feeCryptoCurrencyStatus: CryptoCurrencyStatus by Delegates.notNull() + private var feeCryptoCurrencyStatus: CryptoCurrencyStatus? = null private var balanceJobHolder = JobHolder() private var balanceHidingJobHolder = JobHolder() @@ -328,12 +328,12 @@ internal class SendViewModel @Inject constructor( private suspend fun getFeeCurrencyStatusSync( cryptoCurrencyStatus: CryptoCurrencyStatus, isMultiCurrency: Boolean, - ): CryptoCurrencyStatus { + ): CryptoCurrencyStatus? { return if (isMultiCurrency) { getFeePaidCryptoCurrencyStatusSyncUseCase( userWalletId = userWalletId, cryptoCurrencyStatus = cryptoCurrencyStatus, - ).getOrNull() ?: error("Fee currency is unreachable") + ).getOrNull() } else { cryptoCurrencyStatus } @@ -354,7 +354,7 @@ internal class SendViewModel @Inject constructor( private fun onDataLoaded( currencyStatus: CryptoCurrencyStatus, coinCurrencyStatus: CryptoCurrencyStatus, - feeCurrencyStatus: CryptoCurrencyStatus, + feeCurrencyStatus: CryptoCurrencyStatus?, ) { cryptoCurrencyStatus = currencyStatus coinCryptoCurrencyStatus = coinCurrencyStatus From 7e8b587fbb76ea59c197d5c366f6ddaa57d2e1e7 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 8 Apr 2024 15:30:28 +0500 Subject: [PATCH 21/67] Updated on 2026-08-14 --- .../send/impl/presentation/viewmodel/SendViewModel.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendViewModel.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendViewModel.kt index df9aa391e9..4670b152cc 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendViewModel.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendViewModel.kt @@ -412,7 +412,9 @@ internal class SendViewModel @Inject constructor( private suspend fun UserWallet.toAvailableWallet(): AvailableWallet? { return if (!isMultiCurrency) { val status = getCryptoCurrencyStatusSyncUseCase(walletId).getOrNull() - val address = status?.value?.networkAddress + val address = status?.value?.networkAddress.takeIf { + status?.currency?.network?.id == cryptoCurrency.network.id + } address?.let { AvailableWallet( name = name, From c68c5db506b10f46389b33c22d5778d3f76e4e7a Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 8 Apr 2024 15:31:30 +0500 Subject: [PATCH 22/67] Updated on 2026-08-14 --- .../impl/presentation/state/SendAlertState.kt | 8 ++++ .../state/SendEventStateFactory.kt | 14 ++++++ .../state/amount/AmountNotificationFactory.kt | 38 ---------------- .../state/previewdata/SendClickIntentsStub.kt | 2 +- .../viewmodel/SendClickIntents.kt | 2 +- .../presentation/viewmodel/SendViewModel.kt | 43 ++++++++----------- 6 files changed, 41 insertions(+), 66 deletions(-) delete mode 100644 features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/AmountNotificationFactory.kt diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendAlertState.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendAlertState.kt index 93bb812fae..d3adc2e74c 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendAlertState.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendAlertState.kt @@ -75,4 +75,12 @@ internal sealed class SendAlertState { override val message: TextReference = resourceReference(id = R.string.send_notification_invalid_reserve_amount_text) } + + data class FeeUnreachableError( + override val onConfirmClick: (() -> Unit), + ) : SendAlertState() { + override val title: TextReference = resourceReference(R.string.send_fee_unreachable_error_title) + override val message: TextReference = resourceReference(R.string.send_fee_unreachable_error_text) + override val confirmButtonText = resourceReference(R.string.warning_button_refresh) + } } \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendEventStateFactory.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendEventStateFactory.kt index 2928e3d1f6..58e0a97b65 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendEventStateFactory.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendEventStateFactory.kt @@ -123,4 +123,18 @@ internal class SendEventStateFactory( ), ) } + + fun getFeeUnreachableErrorState(onConsume: () -> Unit): SendUiState { + val state = currentStateProvider() + return state.copy( + event = triggeredEvent( + data = SendEvent.ShowAlert( + SendAlertState.FeeUnreachableError( + onConfirmClick = { clickIntents.feeReload(true) }, + ), + ), + onConsume = onConsume, + ), + ) + } } \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/AmountNotificationFactory.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/AmountNotificationFactory.kt deleted file mode 100644 index 222a8704cf..0000000000 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/AmountNotificationFactory.kt +++ /dev/null @@ -1,38 +0,0 @@ -package com.tangem.features.send.impl.presentation.state.amount - -import com.tangem.features.send.impl.presentation.state.SendUiState -import com.tangem.features.send.impl.presentation.state.SendUiStateType -import com.tangem.features.send.impl.presentation.state.StateRouter -import com.tangem.features.send.impl.presentation.state.SendNotification -import com.tangem.features.send.impl.presentation.state.fee.FeeSelectorState -import com.tangem.features.send.impl.presentation.viewmodel.SendClickIntents -import com.tangem.utils.Provider -import kotlinx.collections.immutable.toImmutableList -import kotlinx.coroutines.flow.filter -import kotlinx.coroutines.flow.map - -internal class AmountNotificationFactory( - private val stateRouterProvider: Provider, - private val currentStateProvider: Provider, - private val clickIntents: SendClickIntents, -) { - - fun create() = stateRouterProvider().currentState - .filter { it.type == SendUiStateType.Amount } - .map { - buildList { - addFeeUnreachableNotification() - }.toImmutableList() - } - - private fun MutableList.addFeeUnreachableNotification() { - val state = currentStateProvider() - val feeState = state.feeState ?: return - - if (feeState.feeSelectorState is FeeSelectorState.Error) { - add( - SendNotification.Warning.NetworkFeeUnreachable(clickIntents::feeReload), - ) - } - } -} \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/previewdata/SendClickIntentsStub.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/previewdata/SendClickIntentsStub.kt index d70b281ffe..67d5b07642 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/previewdata/SendClickIntentsStub.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/previewdata/SendClickIntentsStub.kt @@ -33,7 +33,7 @@ internal object SendClickIntentsStub : SendClickIntents { override fun onRecipientMemoValueChange(value: String) {} - override fun feeReload() {} + override fun feeReload(isToNextState: Boolean) {} override fun onFeeSelectorClick(feeType: FeeType) {} diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendClickIntents.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendClickIntents.kt index c35cf2ba2d..14ed1562af 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendClickIntents.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendClickIntents.kt @@ -38,7 +38,7 @@ internal interface SendClickIntents { // endregion // region Fee - fun feeReload() + fun feeReload(isToNextState: Boolean = false) fun onFeeSelectorClick(feeType: FeeType) diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendViewModel.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendViewModel.kt index 4670b152cc..099bcd007f 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendViewModel.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendViewModel.kt @@ -46,7 +46,6 @@ import com.tangem.features.send.impl.presentation.analytics.SendScreenSource import com.tangem.features.send.impl.presentation.analytics.utils.SendOnNextScreenAnalyticSender import com.tangem.features.send.impl.presentation.domain.AvailableWallet import com.tangem.features.send.impl.presentation.state.* -import com.tangem.features.send.impl.presentation.state.amount.AmountNotificationFactory import com.tangem.features.send.impl.presentation.state.amount.AmountStateFactory import com.tangem.features.send.impl.presentation.state.confirm.SendNotificationFactory import com.tangem.features.send.impl.presentation.state.fee.* @@ -147,12 +146,6 @@ internal class SendViewModel @Inject constructor( feeStateFactory = feeStateFactory, ) - private val amountNotificationFactory = AmountNotificationFactory( - currentStateProvider = Provider { uiState }, - stateRouterProvider = Provider { stateRouter }, - clickIntents = this, - ) - private val feeNotificationFactory = FeeNotificationFactory( currentStateProvider = Provider { uiState }, stateRouterProvider = Provider { stateRouter }, @@ -194,7 +187,6 @@ internal class SendViewModel @Inject constructor( private var memoValidationJobHolder = JobHolder() private var sendNotificationsJobHolder = JobHolder() private var feeNotificationsJobHolder = JobHolder() - private var amountNotificationsJobHolder = JobHolder() private var qrScannerJobHolder = JobHolder() private var sendIdleTimer = 0L @@ -478,16 +470,6 @@ internal class SendViewModel @Inject constructor( .saveIn(feeNotificationsJobHolder) } - private fun updateAmountNotifications() { - amountNotificationFactory.create() - .conflate() - .distinctUntilChanged() - .onEach { uiState = amountStateFactory.getAmountNotificationState(notifications = it) } - .flowOn(dispatchers.io) - .launchIn(viewModelScope) - .saveIn(amountNotificationsJobHolder) - } - // region screen state navigation override fun popBackStack() = stateRouter.popBackStack() override fun onBackClick() { @@ -661,7 +643,7 @@ internal class SendViewModel @Inject constructor( // endregion // region fee - override fun feeReload() = loadFee() + override fun feeReload(isToNextState: Boolean) = loadFee(isToNextState = isToNextState) override fun onFeeSelectorClick(feeType: FeeType) { uiState = feeStateFactory.onFeeSelectedState(feeType) @@ -706,20 +688,30 @@ internal class SendViewModel @Inject constructor( } }, ifLeft = { - if (isShowStatus) uiState = feeStateFactory.onFeeOnErrorState() + onFeeLoadFailed(isShowStatus, isToNextState) }, ) - if (result == null && isShowStatus) { - uiState = feeStateFactory.onFeeOnErrorState() + if (result == null) { + onFeeLoadFailed(isShowStatus, isToNextState) } updateFeeNotifications() - updateAmountNotifications() }.saveIn(feeJobHolder) .invokeOnCompletion { uiState = amountStateFactory.getOnAmountFeeLoadingCancel() } } + private fun onFeeLoadFailed(isShowStatus: Boolean, isToNextState: Boolean) { + when { + isToNextState -> { + uiState = eventStateFactory.getFeeUnreachableErrorState { + uiState = eventStateFactory.onConsumeEventState() + } + } + isShowStatus -> uiState = feeStateFactory.onFeeOnErrorState() + } + } + private suspend fun checkIfSubtractAvailable() { isAmountSubtractAvailable = isAmountSubtractAvailableUseCase(userWalletId, cryptoCurrency).fold( ifRight = { it }, @@ -884,8 +876,7 @@ internal class SendViewModel @Inject constructor( }, ifLeft = { uiState = stateFactory.getSendingStateUpdate(isSending = false) - eventStateFactory.getGenericErrorState( - error = (it as? GetFeeError.DataError)?.cause, + eventStateFactory.getFeeUnreachableErrorState( onConsume = { uiState = eventStateFactory.onConsumeEventState() }, ) }, @@ -895,7 +886,7 @@ internal class SendViewModel @Inject constructor( feeUpdatedState } else { uiState = stateFactory.getSendingStateUpdate(isSending = false) - eventStateFactory.getGenericErrorState( + eventStateFactory.getFeeUnreachableErrorState( onConsume = { uiState = eventStateFactory.onConsumeEventState() }, ) } From 65743285e41e60b65ccc554c8a8d33151be0f3ad Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 8 Apr 2024 15:31:30 +0500 Subject: [PATCH 23/67] Updated on 2026-08-14 --- .../features/send/impl/presentation/state/SendAlertState.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendAlertState.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendAlertState.kt index d3adc2e74c..42a3494b50 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendAlertState.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendAlertState.kt @@ -12,7 +12,9 @@ internal sealed class SendAlertState { abstract val title: TextReference? abstract val message: TextReference open val confirmButtonText: TextReference = resourceReference(id = R.string.common_ok) + // open val cancelButtonText: TextReference = resourceReference(id = R.string.common_cancel) open val onConfirmClick: (() -> Unit)? = null + // open val onCancelClick: (() -> Unit)? = null data class GenericError( override val title: TextReference? = resourceReference(id = R.string.send_alert_transaction_failed_title), From 16b8fae4d75789e7a08356bad7821d1a2748d1b1 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 8 Apr 2024 17:21:35 +0500 Subject: [PATCH 24/67] Updated on 2026-08-14 --- .../inputrow/InputRowEnterInfoAmount.kt | 2 + .../core/ui/utils/DecimalFormatterExt.kt | 10 +- .../impl/presentation/state/SendAlertState.kt | 2 - .../presentation/state/fee/FeeConverter.kt | 10 ++ .../state/fee/SendFeeCustomFieldConverter.kt | 22 ++- .../fee/custom/BitcoinCustomFeeConverter.kt | 139 ++++++++++++++++++ .../state/fee/custom/CustomFeeConverter.kt | 10 ++ .../fee/custom/EthereumCustomFeeConverter.kt | 5 +- .../state/fields/SendTextField.kt | 1 + ...dCustomFeeEthereum.kt => SendCustomFee.kt} | 3 +- .../ui/fee/SendSpeedAndFeeContent.kt | 2 +- .../feature/swap/domain/SwapInteractorImpl.kt | 2 +- gradle/dependencies.toml | 2 +- 13 files changed, 195 insertions(+), 15 deletions(-) create mode 100644 features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/custom/BitcoinCustomFeeConverter.kt create mode 100644 features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/custom/CustomFeeConverter.kt rename features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/fee/{SendCustomFeeEthereum.kt => SendCustomFee.kt} (97%) diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/inputrow/InputRowEnterInfoAmount.kt b/core/ui/src/main/java/com/tangem/core/ui/components/inputrow/InputRowEnterInfoAmount.kt index 00d4221e37..f3d8a0c1cc 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/inputrow/InputRowEnterInfoAmount.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/inputrow/InputRowEnterInfoAmount.kt @@ -57,6 +57,7 @@ fun InputRowEnterInfoAmount( keyboardOptions: KeyboardOptions = KeyboardOptions.Default, keyboardActions: KeyboardActions = KeyboardActions.Default, showDivider: Boolean = false, + isReadOnly: Boolean = false, ) { DividerContainer( modifier = modifier, @@ -83,6 +84,7 @@ fun InputRowEnterInfoAmount( ), onValueChange = onValueChange, color = textColor, + isEnabled = !isReadOnly, textStyle = TangemTheme.typography.body2, keyboardOptions = keyboardOptions, keyboardActions = keyboardActions, diff --git a/core/ui/src/main/java/com/tangem/core/ui/utils/DecimalFormatterExt.kt b/core/ui/src/main/java/com/tangem/core/ui/utils/DecimalFormatterExt.kt index 8cc2cf7bda..7db27cf7f4 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/utils/DecimalFormatterExt.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/utils/DecimalFormatterExt.kt @@ -59,7 +59,7 @@ fun DecimalFormat.getValidatedNumberWithFixedDecimals(text: String, decimals: In return if (filteredChars.count { it == decimalSeparator } == 1) { val beforeDecimal = filteredChars.substringBefore(decimalSeparator) val afterDecimal = filteredChars.substringAfter(decimalSeparator) - beforeDecimal + decimalSeparator + afterDecimal.take(decimals) + decimals.getWithIntegerDecimals(beforeDecimal, decimalSeparator, afterDecimal) } // If there is no dot, just take all digits else { @@ -82,7 +82,7 @@ fun DecimalFormat.formatWithThousands(text: String, decimals: Int): String { .joinToString(thousandsSeparator.toString()) .reversed() val afterDecimal = localizedText.substringAfter(decimalSeparator) - beforeDecimal + decimalSeparator + afterDecimal.take(decimals) + decimals.getWithIntegerDecimals(beforeDecimal, decimalSeparator, afterDecimal) } // If there is no dot, just take all digits else { @@ -150,4 +150,10 @@ fun BigDecimal.parseBigDecimal(decimals: Int, roundingMode: RoundingMode = Round } catch (e: Exception) { "" } +} + +private fun Int.getWithIntegerDecimals(before: String, separator: Char, after: String): String = if (this == 0) { + before +} else { + before + separator + after.take(this) } \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendAlertState.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendAlertState.kt index 42a3494b50..d3adc2e74c 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendAlertState.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendAlertState.kt @@ -12,9 +12,7 @@ internal sealed class SendAlertState { abstract val title: TextReference? abstract val message: TextReference open val confirmButtonText: TextReference = resourceReference(id = R.string.common_ok) - // open val cancelButtonText: TextReference = resourceReference(id = R.string.common_cancel) open val onConfirmClick: (() -> Unit)? = null - // open val onCancelClick: (() -> Unit)? = null data class GenericError( override val title: TextReference? = resourceReference(id = R.string.send_alert_transaction_failed_title), diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeConverter.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeConverter.kt index 2d876ae4e9..1fa99ee291 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeConverter.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeConverter.kt @@ -5,6 +5,7 @@ import com.tangem.blockchain.common.transaction.TransactionFee import com.tangem.core.ui.utils.parseToBigDecimal import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.tokens.model.CryptoCurrencyStatus +import com.tangem.features.send.impl.presentation.state.fee.custom.BitcoinCustomFeeConverter import com.tangem.features.send.impl.presentation.state.fee.custom.EthereumCustomFeeConverter import com.tangem.features.send.impl.presentation.viewmodel.SendClickIntents import com.tangem.utils.Provider @@ -24,6 +25,14 @@ internal class FeeConverter( ) } + private val bitcoinCustomFeeConverter by lazy(LazyThreadSafetyMode.NONE) { + BitcoinCustomFeeConverter( + clickIntents = clickIntents, + appCurrencyProvider = appCurrencyProvider, + feeCryptoCurrencyStatusProvider = feeCryptoCurrencyStatusProvider, + ) + } + override fun convert(value: FeeSelectorState.Content): Fee { return when (val fees = value.fees) { is TransactionFee.Choosable -> { @@ -46,6 +55,7 @@ internal class FeeConverter( } else { when (normalFee) { is Fee.Ethereum -> ethereumCustomFeeConverter.convertBack(normalFee = normalFee, value = customValues) + is Fee.Bitcoin -> bitcoinCustomFeeConverter.convertBack(normalFee = normalFee, value = customValues) else -> { val customFee = customValues.firstOrNull() Fee.Common( diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/SendFeeCustomFieldConverter.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/SendFeeCustomFieldConverter.kt index 79c7a7e318..c9534ea97f 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/SendFeeCustomFieldConverter.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/SendFeeCustomFieldConverter.kt @@ -3,6 +3,7 @@ package com.tangem.features.send.impl.presentation.state.fee import com.tangem.blockchain.common.transaction.Fee import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.tokens.model.CryptoCurrencyStatus +import com.tangem.features.send.impl.presentation.state.fee.custom.BitcoinCustomFeeConverter import com.tangem.features.send.impl.presentation.state.fee.custom.EthereumCustomFeeConverter import com.tangem.features.send.impl.presentation.state.fields.SendTextField import com.tangem.features.send.impl.presentation.viewmodel.SendClickIntents @@ -25,22 +26,35 @@ internal class SendFeeCustomFieldConverter( ) } + private val bitcoinCustomFeeConverter by lazy(LazyThreadSafetyMode.NONE) { + BitcoinCustomFeeConverter( + clickIntents = clickIntents, + appCurrencyProvider = appCurrencyProvider, + feeCryptoCurrencyStatusProvider = feeCryptoCurrencyStatusProvider, + ) + } + override fun convert(value: Fee): ImmutableList { return when (value) { - is Fee.Ethereum -> { - ethereumCustomFeeConverter.convert(value) - } + is Fee.Ethereum -> ethereumCustomFeeConverter.convert(value) + is Fee.Bitcoin -> bitcoinCustomFeeConverter.convert(value) else -> persistentListOf() } } fun onValueChange(feeSelectorState: FeeSelectorState.Content, index: Int, value: String) = feeSelectorState.copy( - customValues = when (feeSelectorState.fees.normal) { + customValues = when (val fee = feeSelectorState.fees.normal) { is Fee.Ethereum -> ethereumCustomFeeConverter.onValueChange( customValues = feeSelectorState.customValues, index = index, value = value, ) + is Fee.Bitcoin -> bitcoinCustomFeeConverter.onValueChange( + customValues = feeSelectorState.customValues, + index = index, + value = value, + txSize = fee.txSize, + ) else -> feeSelectorState.customValues }, ) diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/custom/BitcoinCustomFeeConverter.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/custom/BitcoinCustomFeeConverter.kt new file mode 100644 index 0000000000..6fb6ce20ef --- /dev/null +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/custom/BitcoinCustomFeeConverter.kt @@ -0,0 +1,139 @@ +package com.tangem.features.send.impl.presentation.state.fee.custom + +import androidx.compose.foundation.text.KeyboardActions +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.ui.text.input.ImeAction +import androidx.compose.ui.text.input.KeyboardType +import com.tangem.blockchain.common.transaction.Fee +import com.tangem.common.extensions.isZero +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.utils.BigDecimalFormatter +import com.tangem.core.ui.utils.parseBigDecimal +import com.tangem.core.ui.utils.parseToBigDecimal +import com.tangem.domain.appcurrency.model.AppCurrency +import com.tangem.domain.tokens.model.CryptoCurrencyStatus +import com.tangem.features.send.impl.R +import com.tangem.features.send.impl.presentation.state.fields.SendTextField +import com.tangem.features.send.impl.presentation.viewmodel.SendClickIntents +import com.tangem.utils.Provider +import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.persistentListOf +import kotlinx.collections.immutable.toImmutableList +import java.math.BigDecimal +import java.math.RoundingMode + +internal class BitcoinCustomFeeConverter( + private val clickIntents: SendClickIntents, + private val appCurrencyProvider: Provider, + private val feeCryptoCurrencyStatusProvider: Provider, +) : CustomFeeConverter { + + override fun convert(value: Fee.Bitcoin): ImmutableList { + val feeValue = value.amount.value + return persistentListOf( + SendTextField.CustomFee( + value = feeValue?.parseBigDecimal(value.amount.decimals).orEmpty(), + decimals = value.amount.decimals, + symbol = value.amount.currencySymbol, + onValueChange = { clickIntents.onCustomFeeValueChange(FEE_AMOUNT_INDEX, it) }, + keyboardOptions = KeyboardOptions( + imeAction = ImeAction.Next, + keyboardType = KeyboardType.Number, + ), + title = resourceReference(R.string.send_max_fee), + footer = resourceReference(R.string.send_max_fee_footer), + label = getFeeFormatted(feeValue), + keyboardActions = KeyboardActions(), + isReadonly = true, + ), + SendTextField.CustomFee( + value = toSatoshiPerByte( + amount = feeValue, + decimals = value.amount.decimals, + txSize = value.txSize, + ).toString(), + decimals = SATOSHI_DECIMALS, + symbol = "", + title = resourceReference(R.string.send_gas_price), + footer = resourceReference(R.string.send_gas_price_footer), + onValueChange = { clickIntents.onCustomFeeValueChange(FEE_SATOSHI_INDEX, it) }, + keyboardOptions = KeyboardOptions( + imeAction = if (checkExceedBalance(feeValue)) ImeAction.None else ImeAction.Done, + keyboardType = KeyboardType.Number, + ), + keyboardActions = KeyboardActions(), + ), + ) + } + + override fun convertBack(normalFee: Fee.Bitcoin, value: ImmutableList): Fee.Bitcoin { + val feeAmount = value[FEE_AMOUNT_INDEX].value.parseToBigDecimal(value[FEE_AMOUNT_INDEX].decimals) + val satoshiPerByte = value[FEE_SATOSHI_INDEX].value.parseToBigDecimal(value[FEE_SATOSHI_INDEX].decimals) + return normalFee.copy( + amount = normalFee.amount.copy(value = feeAmount), + satoshiPerByte = satoshiPerByte, + ) + } + + fun onValueChange( + customValues: ImmutableList, + index: Int, + value: String, + txSize: BigDecimal, + ): ImmutableList { + val mutableCustomValues = customValues.toMutableList() + return mutableCustomValues.apply { + if (index == FEE_SATOSHI_INDEX) { + val newSatoshiPerKb = value.parseToBigDecimal(this[FEE_SATOSHI_INDEX].decimals) + val newFeeAmount = newSatoshiPerKb.multiply(txSize) + .movePointLeft(this[FEE_AMOUNT_INDEX].decimals) + .setScale(this[FEE_AMOUNT_INDEX].decimals, RoundingMode.DOWN) + set( + FEE_AMOUNT_INDEX, + this[FEE_AMOUNT_INDEX].copy( + value = newFeeAmount.parseBigDecimal(this[FEE_AMOUNT_INDEX].decimals), + label = getFeeFormatted(newFeeAmount), + ), + ) + set(index, this[index].copy(value = value)) + } + }.toImmutableList() + } + + private fun getFeeFormatted(fee: BigDecimal?): TextReference { + val appCurrency = appCurrencyProvider() + val rate = feeCryptoCurrencyStatusProvider()?.value?.fiatRate + val fiatFee = rate?.let { fee?.multiply(it) } + return stringReference( + BigDecimalFormatter.formatFiatAmount( + fiatAmount = fiatFee, + fiatCurrencyCode = appCurrency.code, + fiatCurrencySymbol = appCurrency.symbol, + ), + ) + } + + private fun checkExceedBalance(feeAmount: BigDecimal?): Boolean { + val cryptoCurrencyStatus = feeCryptoCurrencyStatusProvider() + val currencyCryptoAmount = cryptoCurrencyStatus?.value?.amount ?: BigDecimal.ZERO + + return feeAmount == null || feeAmount.isZero() || feeAmount > currencyCryptoAmount + } + + private fun toSatoshiPerByte(amount: BigDecimal?, decimals: Int, txSize: BigDecimal): BigDecimal? { + val newFeeAmount = amount?.movePointRight(decimals) + return newFeeAmount?.divide( + txSize, + SATOSHI_DECIMALS, + RoundingMode.HALF_UP, + )?.setScale(SATOSHI_DECIMALS, RoundingMode.HALF_UP) + } + + private companion object { + private const val FEE_AMOUNT_INDEX = 0 + private const val FEE_SATOSHI_INDEX = 1 + private const val SATOSHI_DECIMALS = 0 + } +} \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/custom/CustomFeeConverter.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/custom/CustomFeeConverter.kt new file mode 100644 index 0000000000..25764269ec --- /dev/null +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/custom/CustomFeeConverter.kt @@ -0,0 +1,10 @@ +package com.tangem.features.send.impl.presentation.state.fee.custom + +import com.tangem.blockchain.common.transaction.Fee +import com.tangem.features.send.impl.presentation.state.fields.SendTextField +import com.tangem.utils.converter.Converter +import kotlinx.collections.immutable.ImmutableList + +internal interface CustomFeeConverter : Converter> { + fun convertBack(normalFee: T, value: ImmutableList): T +} \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/custom/EthereumCustomFeeConverter.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/custom/EthereumCustomFeeConverter.kt index 97726412f7..a9bf8e5607 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/custom/EthereumCustomFeeConverter.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/custom/EthereumCustomFeeConverter.kt @@ -18,7 +18,6 @@ import com.tangem.features.send.impl.R import com.tangem.features.send.impl.presentation.state.fields.SendTextField import com.tangem.features.send.impl.presentation.viewmodel.SendClickIntents import com.tangem.utils.Provider -import com.tangem.utils.converter.Converter import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toImmutableList @@ -29,7 +28,7 @@ internal class EthereumCustomFeeConverter( private val clickIntents: SendClickIntents, private val appCurrencyProvider: Provider, private val feeCryptoCurrencyStatusProvider: Provider, -) : Converter> { +) : CustomFeeConverter { override fun convert(value: Fee.Ethereum): ImmutableList { val feeValue = value.amount.value @@ -77,7 +76,7 @@ internal class EthereumCustomFeeConverter( ) } - fun convertBack(normalFee: Fee.Ethereum, value: ImmutableList): Fee.Ethereum { + override fun convertBack(normalFee: Fee.Ethereum, value: ImmutableList): Fee.Ethereum { val feeAmount = value[FEE_AMOUNT].value.parseToBigDecimal(value[FEE_AMOUNT].decimals) val gasPrice = value[GAS_PRICE].value.parseToBigDecimal(GAS_DECIMALS).toBigInteger() val gasLimit = value[GAS_LIMIT].value.parseToBigDecimal(GAS_DECIMALS).toBigInteger() diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fields/SendTextField.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fields/SendTextField.kt index 8db8a2fc02..266f9e8f6e 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fields/SendTextField.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fields/SendTextField.kt @@ -64,5 +64,6 @@ internal sealed class SendTextField { val title: TextReference, val footer: TextReference, val label: TextReference? = null, + val isReadonly: Boolean = false, ) : SendTextField() } \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/fee/SendCustomFeeEthereum.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/fee/SendCustomFee.kt similarity index 97% rename from features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/fee/SendCustomFeeEthereum.kt rename to features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/fee/SendCustomFee.kt index 50fde69ede..79cff9da25 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/fee/SendCustomFeeEthereum.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/fee/SendCustomFee.kt @@ -17,7 +17,7 @@ import com.tangem.features.send.impl.presentation.ui.common.FooterContainer import kotlinx.collections.immutable.ImmutableList @Composable -internal fun SendCustomFeeEthereum( +internal fun SendCustomFee( customValues: ImmutableList, selectedFee: FeeType, hasNotifications: Boolean, @@ -55,6 +55,7 @@ internal fun SendCustomFeeEthereum( keyboardActions = value.keyboardActions, onValueChange = value.onValueChange, showDivider = false, + isReadOnly = value.isReadonly, modifier = Modifier .background( color = TangemTheme.colors.background.action, diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/fee/SendSpeedAndFeeContent.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/fee/SendSpeedAndFeeContent.kt index ee4055bff5..6281bfd810 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/fee/SendSpeedAndFeeContent.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/fee/SendSpeedAndFeeContent.kt @@ -64,7 +64,7 @@ internal fun LazyListScope.customFee( item( key = FEE_CUSTOM_KEY, ) { - SendCustomFeeEthereum( + SendCustomFee( customValues = feeSendState.customValues, selectedFee = feeSendState.selectedFee, hasNotifications = hasNotifications, diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt index 08b31e9a2e..966b619bf8 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt @@ -1406,10 +1406,10 @@ internal class SwapInteractorImpl @Inject constructor( private fun Fee.getGasLimit(): Int { return when (this) { - is Fee.Common -> 0 is Fee.Ethereum -> gasLimit.toInt() is Fee.VeChain -> gasLimit.toInt() is Fee.Aptos -> gasLimit.toInt() + else -> 0 } } diff --git a/gradle/dependencies.toml b/gradle/dependencies.toml index 8731df13aa..d02fccec51 100644 --- a/gradle/dependencies.toml +++ b/gradle/dependencies.toml @@ -85,7 +85,7 @@ leakcanary = "2.13" # endregion Other libraries # region Tangem -tangemBlockchainSdk = "release-app_5.9-560" +tangemBlockchainSdk = "release-app_5.9-580" #tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds tangemCardSdk = "release-app_5.9-340" #tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^ From 524b69f5f5b20b07c85d30071225b9946d4db318 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 9 Apr 2024 15:48:51 +0500 Subject: [PATCH 25/67] Updated on 2026-08-14 --- .../send/impl/presentation/SendFragment.kt | 4 ++- .../impl/presentation/ui/SendEventEffect.kt | 6 ++++ .../send/impl/presentation/ui/SendScreen.kt | 33 ++++++++----------- .../ui/amount/SendAmountContent.kt | 6 +++- .../presentation/ui/common/Notifications.kt | 10 +----- .../impl/presentation/ui/fee/SendCustomFee.kt | 13 ++++---- .../ui/fee/SendSpeedAndFeeContent.kt | 4 ++- .../ui/recipient/SendRecipientContent.kt | 7 ++-- .../impl/presentation/ui/send/SendContent.kt | 6 +++- .../presentation/viewmodel/SendViewModel.kt | 2 +- 10 files changed, 49 insertions(+), 42 deletions(-) diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/SendFragment.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/SendFragment.kt index 0a3521b1eb..cebe88712f 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/SendFragment.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/SendFragment.kt @@ -5,6 +5,7 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.fragment.app.viewModels import androidx.lifecycle.Lifecycle +import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.flowWithLifecycle import androidx.lifecycle.lifecycleScope import arrow.core.getOrElse @@ -73,7 +74,8 @@ internal class SendFragment : ComposeFragment() { SystemBarsEffect { setSystemBarsColor(systemBarsColor) } - SendScreen(viewModel.uiState, viewModel.stateRouter.currentState) + val currentState = viewModel.stateRouter.currentState.collectAsStateWithLifecycle() + SendScreen(viewModel.uiState, currentState.value) } override fun onDestroy() { diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/SendEventEffect.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/SendEventEffect.kt index 84e638eb8a..53378ca7f7 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/SendEventEffect.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/SendEventEffect.kt @@ -3,6 +3,7 @@ package com.tangem.features.send.impl.presentation.ui import androidx.compose.material3.SnackbarHostState import androidx.compose.runtime.* import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalSoftwareKeyboardController import androidx.compose.ui.res.stringResource import com.tangem.core.ui.components.BasicDialog import com.tangem.core.ui.components.DialogButton @@ -18,6 +19,11 @@ internal fun SendEventEffect(event: StateEvent, snackbarHostState: Sn val resources = LocalContext.current.resources var alertConfig by remember { mutableStateOf(value = null) } + val keyboardController = LocalSoftwareKeyboardController.current + LaunchedEffect(key1 = alertConfig) { + keyboardController?.hide() + } + alertConfig?.let { SendAlert(state = it, onDismiss = { alertConfig = null }) } diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/SendScreen.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/SendScreen.kt index fe28791ec9..55bebe81a7 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/SendScreen.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/SendScreen.kt @@ -11,7 +11,6 @@ import androidx.compose.material3.SnackbarHostState import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.tangem.core.ui.components.appbar.AppBarWithBackButtonAndIcon import com.tangem.core.ui.extensions.resolveReference import com.tangem.core.ui.extensions.resourceReference @@ -25,11 +24,9 @@ import com.tangem.features.send.impl.presentation.ui.amount.SendAmountContent import com.tangem.features.send.impl.presentation.ui.fee.SendSpeedAndFeeContent import com.tangem.features.send.impl.presentation.ui.recipient.SendRecipientContent import com.tangem.features.send.impl.presentation.ui.send.SendContent -import kotlinx.coroutines.flow.StateFlow @Composable -internal fun SendScreen(uiState: SendUiState, currentStateFlow: StateFlow) { - val currentState = currentStateFlow.collectAsStateWithLifecycle() +internal fun SendScreen(uiState: SendUiState, currentState: SendUiCurrentScreen) { val snackbarHostState = remember { SnackbarHostState() } val sendState = uiState.sendState ?: return BackHandler { uiState.clickIntents.onBackClick() } @@ -37,11 +34,10 @@ internal fun SendScreen(uiState: SendUiState, currentStateFlow: StateFlow resourceReference(R.string.send_amount_label) SendUiStateType.Recipient -> resourceReference(R.string.send_recipient_label) SendUiStateType.Fee -> resourceReference(R.string.common_fee_selector_title) @@ -52,13 +48,13 @@ internal fun SendScreen(uiState: SendUiState, currentStateFlow: StateFlow null } - val isSending = currentState.value.type == SendUiStateType.Send && !uiState.sendState.isSuccess + val isSending = currentState.type == SendUiStateType.Send && !uiState.sendState.isSuccess val subtitleRes = if (isSending) { uiState.amountState?.walletName } else { null } - val iconRes = if (currentState.value.type == SendUiStateType.Recipient) { + val iconRes = if (currentState.type == SendUiStateType.Recipient) { R.drawable.ic_qrcode_scan_24 } else { null @@ -74,17 +70,16 @@ internal fun SendScreen(uiState: SendUiState, currentStateFlow: StateFlow item::class.java }, contentType = { _, item -> item::class.java }, itemContent = { i, item -> - val bottomPadding = if (i == notifications.lastIndex) { - TangemTheme.dimens.spacing72 - } else { - TangemTheme.dimens.spacing0 - } val topPadding = if (i == 0 && hasPaddingAbove) { TangemTheme.dimens.spacing0 } else { @@ -34,10 +29,7 @@ internal fun LazyListScope.notifications( Notification( config = item.config, modifier = modifier - .padding( - top = topPadding, - bottom = bottomPadding, - ) + .padding(top = topPadding) .animateItemPlacement(), containerColor = when (item) { is SendNotification.Error.ExceedsBalance, diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/fee/SendCustomFee.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/fee/SendCustomFee.kt index 79cff9da25..1c7e332844 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/fee/SendCustomFee.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/fee/SendCustomFee.kt @@ -29,20 +29,19 @@ internal fun SendCustomFee( enter = expandVertically().plus(fadeIn()), exit = shrinkVertically().plus(fadeOut()), ) { + val bottomPadding = if (hasNotifications) { + TangemTheme.dimens.spacing12 + } else { + TangemTheme.dimens.spacing0 + } Column( verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing12), - modifier = modifier, + modifier = modifier.padding(bottom = bottomPadding), ) { repeat(customValues.size) { index -> val value = customValues[index] - val bottomPadding = if (index == customValues.lastIndex && !hasNotifications) { - TangemTheme.dimens.spacing72 - } else { - TangemTheme.dimens.spacing0 - } FooterContainer( footer = value.footer.resolveReference(), - modifier = Modifier.padding(bottom = bottomPadding), ) { if (value.label != null) { InputRowEnterInfoAmount( diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/fee/SendSpeedAndFeeContent.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/fee/SendSpeedAndFeeContent.kt index 6281bfd810..88f4a37a85 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/fee/SendSpeedAndFeeContent.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/fee/SendSpeedAndFeeContent.kt @@ -31,7 +31,9 @@ internal fun SendSpeedAndFeeContent(state: SendStates.FeeState?, clickIntents: S .fillMaxSize() .background(TangemTheme.colors.background.tertiary) .padding( - horizontal = TangemTheme.dimens.spacing16, + start = TangemTheme.dimens.spacing16, + end = TangemTheme.dimens.spacing16, + bottom = TangemTheme.dimens.spacing16, ), ) { feeSelector(state, clickIntents) diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/recipient/SendRecipientContent.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/recipient/SendRecipientContent.kt index 539d82e49c..eb9b2b544a 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/recipient/SendRecipientContent.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/recipient/SendRecipientContent.kt @@ -51,7 +51,11 @@ internal fun SendRecipientContent( modifier = Modifier .fillMaxSize() .background(TangemTheme.colors.background.tertiary) - .padding(horizontal = TangemTheme.dimens.spacing16), + .padding( + start = TangemTheme.dimens.spacing16, + end = TangemTheme.dimens.spacing16, + bottom = TangemTheme.dimens.spacing16, + ), ) { addressItem( address = address, @@ -222,7 +226,6 @@ private fun LazyListScope.listItem( .then( if (isLast && index == list.lastIndex) { Modifier - .padding(bottom = TangemTheme.dimens.spacing72) .clip( shape = RoundedCornerShape( bottomStart = TangemTheme.dimens.radius16, diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/send/SendContent.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/send/SendContent.kt index 78809756c4..0c92c24fd5 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/send/SendContent.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/send/SendContent.kt @@ -35,7 +35,11 @@ internal fun SendContent(uiState: SendUiState) { LazyColumn( modifier = Modifier .fillMaxSize() - .padding(horizontal = TangemTheme.dimens.spacing16), + .padding( + start = TangemTheme.dimens.spacing16, + end = TangemTheme.dimens.spacing16, + bottom = TangemTheme.dimens.spacing16, + ), ) { blocks(uiState) tapHelp(isDisplay = sendState.showTapHelp) diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendViewModel.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendViewModel.kt index 099bcd007f..069a34f6a3 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendViewModel.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendViewModel.kt @@ -465,7 +465,7 @@ internal class SendViewModel @Inject constructor( .conflate() .distinctUntilChanged() .onEach { uiState = feeStateFactory.getFeeNotificationState(notifications = it) } - .flowOn(dispatchers.io) + .flowOn(dispatchers.main) .launchIn(viewModelScope) .saveIn(feeNotificationsJobHolder) } From 56baebe95c651d82c3031e6bc4d78f2b6837dd23 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 9 Apr 2024 15:51:35 +0500 Subject: [PATCH 26/67] Updated on 2026-08-14 --- .../com/tangem/domain/tokens/GetCurrencyWarningsUseCase.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCurrencyWarningsUseCase.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCurrencyWarningsUseCase.kt index 6e18152f25..eded0146fc 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCurrencyWarningsUseCase.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCurrencyWarningsUseCase.kt @@ -197,7 +197,7 @@ class GetCurrencyWarningsUseCase( coinCurrency = coinStatus.currency, ) } - feePaidCurrency is FeePaidCurrency.SameCurrency && !tokenStatus.value.amount.isZero() -> { + feePaidCurrency is FeePaidCurrency.SameCurrency && tokenStatus.value.amount.isZero() -> { CryptoCurrencyWarning.BalanceNotEnoughForFee( tokenCurrency = tokenStatus.currency, coinCurrency = coinStatus.currency, From 23b9a7bfd1286a9e610a52bdea385631b9ed2a8a Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 5 Apr 2024 15:32:12 +0100 Subject: [PATCH 27/67] Updated on 2026-08-14 --- app/src/main/assets/testnet_tokens.json | 10 +++++++++ .../core/ui/extensions/BlockchainIcons.kt | 5 +++++ core/ui/src/main/res/drawable/ic_base_22.xml | 18 +++++++++++++++ core/ui/src/main/res/drawable/img_base_22.xml | 16 ++++++++++++++ .../QuotesUnsupportedCurrenciesIdAdapter.kt | 1 + .../domain/common/extensions/Blockchain.kt | 5 +++++ .../DefaultWalletManagersFacade.kt | 22 +++++++++++++++++-- gradle/dependencies.toml | 4 ++-- 8 files changed, 77 insertions(+), 4 deletions(-) create mode 100644 core/ui/src/main/res/drawable/ic_base_22.xml create mode 100644 core/ui/src/main/res/drawable/img_base_22.xml diff --git a/app/src/main/assets/testnet_tokens.json b/app/src/main/assets/testnet_tokens.json index 423202eeda..e10692c072 100644 --- a/app/src/main/assets/testnet_tokens.json +++ b/app/src/main/assets/testnet_tokens.json @@ -678,6 +678,16 @@ "networkId": "polygon-zkevm/test" } ] + }, + { + "id": "base-ethereum", + "name": "Base", + "symbol": "ETH", + "networks": [ + { + "networkId": "base/test" + } + ] } ] } diff --git a/core/ui/src/main/java/com/tangem/core/ui/extensions/BlockchainIcons.kt b/core/ui/src/main/java/com/tangem/core/ui/extensions/BlockchainIcons.kt index 0a77e69e7e..1a95df0142 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/extensions/BlockchainIcons.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/extensions/BlockchainIcons.kt @@ -66,6 +66,7 @@ fun getActiveIconRes(blockchainId: String): Int { "flare", "flare/test" -> R.drawable.img_flare_22 "taraxa", "taraxa/test" -> R.drawable.img_taraxa_22 "radiant" -> R.drawable.img_radiant_22 + "base" -> R.drawable.img_base_22 else -> R.drawable.ic_alert_24 } } @@ -133,6 +134,7 @@ fun getActiveIconResByNetworkId(networkId: String): Int { "flare-network", "flare-network/test" -> R.drawable.img_flare_22 "taraxa", "taraxa/test" -> R.drawable.img_taraxa_22 "radiant" -> R.drawable.img_radiant_22 + "base" -> R.drawable.img_base_22 else -> R.drawable.ic_alert_24 } } @@ -197,6 +199,7 @@ fun getActiveIconResByCoinId(coinId: String): Int { "flare-networks" -> R.drawable.img_flare_22 "taraxa" -> R.drawable.img_taraxa_22 "radiant" -> R.drawable.img_radiant_22 + "base" -> R.drawable.img_base_22 else -> R.drawable.ic_alert_24 } } @@ -264,6 +267,7 @@ fun getGreyedOutIconRes(blockchainId: String): Int { "flare", "flare/test" -> R.drawable.ic_flare_22 "taraxa", "taraxa/test" -> R.drawable.ic_taraxa_22 "radiant" -> R.drawable.ic_radiant_22 + "base", "base/test" -> R.drawable.ic_base_22 else -> R.drawable.ic_alert_24 } } @@ -331,6 +335,7 @@ fun getGreyedOutIconResByNetworkId(networkId: String): Int { "flare-network", "flare-network/test" -> R.drawable.ic_flare_22 "taraxa", "taraxa/test" -> R.drawable.ic_taraxa_22 "radiant" -> R.drawable.ic_radiant_22 + "base", "base/test" -> R.drawable.ic_base_22 else -> R.drawable.ic_alert_24 } } \ No newline at end of file diff --git a/core/ui/src/main/res/drawable/ic_base_22.xml b/core/ui/src/main/res/drawable/ic_base_22.xml new file mode 100644 index 0000000000..7c135649e4 --- /dev/null +++ b/core/ui/src/main/res/drawable/ic_base_22.xml @@ -0,0 +1,18 @@ + + + + + + + + + diff --git a/core/ui/src/main/res/drawable/img_base_22.xml b/core/ui/src/main/res/drawable/img_base_22.xml new file mode 100644 index 0000000000..a426d0c8b2 --- /dev/null +++ b/core/ui/src/main/res/drawable/img_base_22.xml @@ -0,0 +1,16 @@ + + + + + + + diff --git a/data/tokens/src/main/kotlin/com/tangem/data/tokens/utils/QuotesUnsupportedCurrenciesIdAdapter.kt b/data/tokens/src/main/kotlin/com/tangem/data/tokens/utils/QuotesUnsupportedCurrenciesIdAdapter.kt index 6b2779299c..0e69a31330 100644 --- a/data/tokens/src/main/kotlin/com/tangem/data/tokens/utils/QuotesUnsupportedCurrenciesIdAdapter.kt +++ b/data/tokens/src/main/kotlin/com/tangem/data/tokens/utils/QuotesUnsupportedCurrenciesIdAdapter.kt @@ -55,6 +55,7 @@ internal class QuotesUnsupportedCurrenciesIdAdapter { "manta-network-ethereum" to "ethereum", "polygon-zkevm-ethereum" to "ethereum", "aurora-ethereum" to "ethereum", + "base-ethereum" to "ethereum", ) } } \ No newline at end of file diff --git a/domain/legacy/src/main/java/com/tangem/domain/common/extensions/Blockchain.kt b/domain/legacy/src/main/java/com/tangem/domain/common/extensions/Blockchain.kt index e3ed6aabed..d3a65d7373 100644 --- a/domain/legacy/src/main/java/com/tangem/domain/common/extensions/Blockchain.kt +++ b/domain/legacy/src/main/java/com/tangem/domain/common/extensions/Blockchain.kt @@ -112,6 +112,8 @@ fun Blockchain.Companion.fromNetworkId(networkId: String): Blockchain? { "flare-network/test" -> Blockchain.FlareTestnet "taraxa" -> Blockchain.Taraxa "taraxa/test" -> Blockchain.TaraxaTestnet + "base" -> Blockchain.Base + "base/test" -> Blockchain.BaseTestnet else -> null } } @@ -225,6 +227,8 @@ fun Blockchain.toNetworkId(): String { Blockchain.FlareTestnet -> "flare-network/test" Blockchain.Taraxa -> "taraxa" Blockchain.TaraxaTestnet -> "taraxa/test" + Blockchain.Base -> "base" + Blockchain.BaseTestnet -> "base/test" } } @@ -297,6 +301,7 @@ fun Blockchain.toCoinId(): String { Blockchain.Mantle, Blockchain.MantleTestnet -> "mantle" Blockchain.Flare, Blockchain.FlareTestnet -> "flare-networks" Blockchain.Taraxa, Blockchain.TaraxaTestnet -> "taraxa" + Blockchain.Base, Blockchain.BaseTestnet -> "base" } } diff --git a/domain/legacy/src/main/java/com/tangem/domain/walletmanager/DefaultWalletManagersFacade.kt b/domain/legacy/src/main/java/com/tangem/domain/walletmanager/DefaultWalletManagersFacade.kt index 276758748b..951d8ff4ef 100644 --- a/domain/legacy/src/main/java/com/tangem/domain/walletmanager/DefaultWalletManagersFacade.kt +++ b/domain/legacy/src/main/java/com/tangem/domain/walletmanager/DefaultWalletManagersFacade.kt @@ -192,7 +192,16 @@ class DefaultWalletManagersFacade( address = walletManager.wallet.address, filterType = when (currency) { is CryptoCurrency.Coin -> TransactionHistoryRequest.FilterType.Coin - is CryptoCurrency.Token -> TransactionHistoryRequest.FilterType.Contract(currency.contractAddress) + is CryptoCurrency.Token -> { + val blockchainToken = Token( + name = currency.name, + symbol = currency.symbol, + contractAddress = currency.contractAddress, + decimals = currency.decimals, + id = currency.id.rawCurrencyId, + ) + TransactionHistoryRequest.FilterType.Contract(blockchainToken) + } }, ) .let(txHistoryStateConverter::convert) @@ -221,7 +230,16 @@ class DefaultWalletManagersFacade( pageSize = pageSize, filterType = when (currency) { is CryptoCurrency.Coin -> TransactionHistoryRequest.FilterType.Coin - is CryptoCurrency.Token -> TransactionHistoryRequest.FilterType.Contract(currency.contractAddress) + is CryptoCurrency.Token -> { + val blockchainToken = Token( + name = currency.name, + symbol = currency.symbol, + contractAddress = currency.contractAddress, + decimals = currency.decimals, + id = currency.id.rawCurrencyId, + ) + TransactionHistoryRequest.FilterType.Contract(blockchainToken) + } }, ), ) diff --git a/gradle/dependencies.toml b/gradle/dependencies.toml index d02fccec51..4995c29d6a 100644 --- a/gradle/dependencies.toml +++ b/gradle/dependencies.toml @@ -85,9 +85,9 @@ leakcanary = "2.13" # endregion Other libraries # region Tangem -tangemBlockchainSdk = "release-app_5.9-580" +tangemBlockchainSdk = "release-app_5.9-582" #tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds -tangemCardSdk = "release-app_5.9-340" +tangemCardSdk = "release-app_5.9-343" #tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^ # endregion Tangem From 6d325ab00804816e53f12c41da1f41c5f35e53b6 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 9 Apr 2024 19:45:15 +0100 Subject: [PATCH 28/67] Updated on 2026-08-14 --- .../DefaultWalletManagersFacade.kt | 22 ++----------------- 1 file changed, 2 insertions(+), 20 deletions(-) diff --git a/domain/legacy/src/main/java/com/tangem/domain/walletmanager/DefaultWalletManagersFacade.kt b/domain/legacy/src/main/java/com/tangem/domain/walletmanager/DefaultWalletManagersFacade.kt index 951d8ff4ef..276758748b 100644 --- a/domain/legacy/src/main/java/com/tangem/domain/walletmanager/DefaultWalletManagersFacade.kt +++ b/domain/legacy/src/main/java/com/tangem/domain/walletmanager/DefaultWalletManagersFacade.kt @@ -192,16 +192,7 @@ class DefaultWalletManagersFacade( address = walletManager.wallet.address, filterType = when (currency) { is CryptoCurrency.Coin -> TransactionHistoryRequest.FilterType.Coin - is CryptoCurrency.Token -> { - val blockchainToken = Token( - name = currency.name, - symbol = currency.symbol, - contractAddress = currency.contractAddress, - decimals = currency.decimals, - id = currency.id.rawCurrencyId, - ) - TransactionHistoryRequest.FilterType.Contract(blockchainToken) - } + is CryptoCurrency.Token -> TransactionHistoryRequest.FilterType.Contract(currency.contractAddress) }, ) .let(txHistoryStateConverter::convert) @@ -230,16 +221,7 @@ class DefaultWalletManagersFacade( pageSize = pageSize, filterType = when (currency) { is CryptoCurrency.Coin -> TransactionHistoryRequest.FilterType.Coin - is CryptoCurrency.Token -> { - val blockchainToken = Token( - name = currency.name, - symbol = currency.symbol, - contractAddress = currency.contractAddress, - decimals = currency.decimals, - id = currency.id.rawCurrencyId, - ) - TransactionHistoryRequest.FilterType.Contract(blockchainToken) - } + is CryptoCurrency.Token -> TransactionHistoryRequest.FilterType.Contract(currency.contractAddress) }, ), ) From 06fae63efa7e54f3f8e3f9a43006ae6b98968d33 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 10 Apr 2024 11:53:17 +0500 Subject: [PATCH 29/67] Updated on 2026-08-14 --- .../ui/components/fields/SimpleTextField.kt | 17 +++++++++++++---- .../state/confirm/SendNotificationFactory.kt | 8 +++----- .../presentation/viewmodel/SendViewModel.kt | 1 + 3 files changed, 17 insertions(+), 9 deletions(-) diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/fields/SimpleTextField.kt b/core/ui/src/main/java/com/tangem/core/ui/components/fields/SimpleTextField.kt index 6d31ebeaef..ed91b52667 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/fields/SimpleTextField.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/fields/SimpleTextField.kt @@ -71,7 +71,18 @@ fun SimpleTextField( } } - var lastTextValue by remember(value) { mutableStateOf(value) } + var lastTextValue by remember(value) { + derivedStateOf { + val isSelectionLastIndex = textFieldValueState.selection.end == textFieldValueState.text.lastIndex + if (textFieldValueState.text.isBlank() || isSelectionLastIndex) { + textFieldValueState = textFieldValueState.copy( + text = value, + selection = getValueRange(value), + ) + } + } + mutableStateOf(value) + } CompositionLocalProvider(LocalTextSelectionColors provides customTextSelectionColors) { BasicTextField( @@ -82,9 +93,7 @@ fun SimpleTextField( val stringChangedSinceLastInvocation = lastTextValue != newTextFieldValueState.text lastTextValue = newTextFieldValueState.text - if (stringChangedSinceLastInvocation) { - onValueChange(newTextFieldValueState.text) - } + if (stringChangedSinceLastInvocation) onValueChange(newTextFieldValueState.text) }, textStyle = textStyle.copy(color = color), cursorBrush = SolidColor(TangemTheme.colors.text.primary1), diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/confirm/SendNotificationFactory.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/confirm/SendNotificationFactory.kt index 8b5e5530c2..782b8beb8e 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/confirm/SendNotificationFactory.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/confirm/SendNotificationFactory.kt @@ -17,10 +17,6 @@ import com.tangem.domain.wallets.models.UserWallet import com.tangem.features.send.impl.R import com.tangem.features.send.impl.presentation.analytics.SendAnalyticEvents import com.tangem.features.send.impl.presentation.state.* -import com.tangem.features.send.impl.presentation.state.SendNotification -import com.tangem.features.send.impl.presentation.state.SendStates -import com.tangem.features.send.impl.presentation.state.SendUiState -import com.tangem.features.send.impl.presentation.state.StateRouter import com.tangem.features.send.impl.presentation.state.fee.FeeSelectorState import com.tangem.features.send.impl.presentation.state.fee.FeeType import com.tangem.features.send.impl.presentation.viewmodel.SendClickIntents @@ -38,6 +34,7 @@ import java.math.BigDecimal internal class SendNotificationFactory( private val cryptoCurrencyStatusProvider: Provider, private val coinCryptoCurrencyStatusProvider: Provider, + private val feePaidCryptoCurrencyStatusProvider: Provider, private val currentStateProvider: Provider, private val userWalletProvider: Provider, private val currencyChecksRepository: CurrencyChecksRepository, @@ -91,10 +88,11 @@ internal class SendNotificationFactory( ) { val cryptoCurrencyStatus = cryptoCurrencyStatusProvider() val coinCryptoCurrencyStatus = coinCryptoCurrencyStatusProvider() + val feePaidCryptoCurrencyStatus = feePaidCryptoCurrencyStatusProvider() val cryptoAmount = cryptoCurrencyStatus.value.amount ?: BigDecimal.ZERO val coinCryptoAmount = coinCryptoCurrencyStatus.value.amount ?: BigDecimal.ZERO - val showNotification = if (cryptoCurrencyStatus.currency is CryptoCurrency.Token) { + val showNotification = if (cryptoCurrencyStatus.currency.id == feePaidCryptoCurrencyStatus?.currency?.id) { receivedAmount > cryptoAmount || feeAmount > coinCryptoAmount } else { receivedAmount + feeAmount > cryptoAmount diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendViewModel.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendViewModel.kt index 069a34f6a3..99e752dd9f 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendViewModel.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendViewModel.kt @@ -155,6 +155,7 @@ internal class SendViewModel @Inject constructor( private val sendNotificationFactory = SendNotificationFactory( cryptoCurrencyStatusProvider = Provider { cryptoCurrencyStatus }, coinCryptoCurrencyStatusProvider = Provider { coinCryptoCurrencyStatus }, + feePaidCryptoCurrencyStatusProvider = Provider { feeCryptoCurrencyStatus }, currentStateProvider = Provider { uiState }, userWalletProvider = Provider { userWallet }, stateRouterProvider = Provider { stateRouter }, From 505400888ddd1178e6d3daa917b5a0f5118ae008 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 10 Apr 2024 10:10:09 +0100 Subject: [PATCH 30/67] Updated on 2026-08-14 --- .../main/java/com/tangem/domain/common/extensions/Blockchain.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/domain/legacy/src/main/java/com/tangem/domain/common/extensions/Blockchain.kt b/domain/legacy/src/main/java/com/tangem/domain/common/extensions/Blockchain.kt index d3a65d7373..ff110169a6 100644 --- a/domain/legacy/src/main/java/com/tangem/domain/common/extensions/Blockchain.kt +++ b/domain/legacy/src/main/java/com/tangem/domain/common/extensions/Blockchain.kt @@ -301,7 +301,7 @@ fun Blockchain.toCoinId(): String { Blockchain.Mantle, Blockchain.MantleTestnet -> "mantle" Blockchain.Flare, Blockchain.FlareTestnet -> "flare-networks" Blockchain.Taraxa, Blockchain.TaraxaTestnet -> "taraxa" - Blockchain.Base, Blockchain.BaseTestnet -> "base" + Blockchain.Base, Blockchain.BaseTestnet -> "base-ethereum" } } From 230aff6ed66c8fcc9fcbb845ff08f2261a218c19 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 11 Apr 2024 16:09:32 +0500 Subject: [PATCH 31/67] Updated on 2026-08-14 --- .../transaction/error/SendTransactionError.kt | 2 +- .../usecase/SendTransactionUseCase.kt | 7 ++- .../state/SendTransactionAlertConverter.kt | 4 +- .../state/confirm/SendNotificationFactory.kt | 45 +++++++++---------- .../com/tangem/lib/crypto/BlockchainUtils.kt | 5 +++ 5 files changed, 34 insertions(+), 29 deletions(-) diff --git a/domain/transaction/src/main/java/com/tangem/domain/transaction/error/SendTransactionError.kt b/domain/transaction/src/main/java/com/tangem/domain/transaction/error/SendTransactionError.kt index 753f45e4b4..22b7991290 100644 --- a/domain/transaction/src/main/java/com/tangem/domain/transaction/error/SendTransactionError.kt +++ b/domain/transaction/src/main/java/com/tangem/domain/transaction/error/SendTransactionError.kt @@ -8,7 +8,7 @@ sealed class SendTransactionError { data class DataError(val message: String?) : SendTransactionError() - data class NetworkError(val message: String?) : SendTransactionError() + data class NetworkError(val message: String?, val code: String?) : SendTransactionError() data class BlockchainSdkError(val code: Int, val message: String?) : SendTransactionError() diff --git a/domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/SendTransactionUseCase.kt b/domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/SendTransactionUseCase.kt index 059a672c70..d2c9cbd085 100644 --- a/domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/SendTransactionUseCase.kt +++ b/domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/SendTransactionUseCase.kt @@ -67,7 +67,12 @@ class SendTransactionUseCase( } private fun handleError(result: SimpleResult.Failure): SendTransactionError { - if (ResultChecker.isNetworkError(result)) return SendTransactionError.NetworkError(result.error.message) + if (ResultChecker.isNetworkError(result)) { + return SendTransactionError.NetworkError( + code = result.error.message, + message = result.error.customMessage, + ) + } val error = result.error as? BlockchainSdkError ?: return SendTransactionError.UnknownError() return when (error) { is BlockchainSdkError.WrappedTangemError -> { diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendTransactionAlertConverter.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendTransactionAlertConverter.kt index a35b421a16..ad5d16e175 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendTransactionAlertConverter.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendTransactionAlertConverter.kt @@ -29,8 +29,8 @@ internal class SendTransactionAlertConverter( onConfirmClick = { clickIntents.onFailedTxEmailClick(value.message.orEmpty()) }, ) is SendTransactionError.NetworkError -> SendAlertState.TransactionError( - code = "", - cause = value.message, + code = value.code.orEmpty(), + cause = value.message.orEmpty(), onConfirmClick = { clickIntents.onFailedTxEmailClick(value.message.orEmpty()) }, ) is SendTransactionError.UnknownError -> SendAlertState.TransactionError( diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/confirm/SendNotificationFactory.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/confirm/SendNotificationFactory.kt index 782b8beb8e..709779d1df 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/confirm/SendNotificationFactory.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/confirm/SendNotificationFactory.kt @@ -20,6 +20,7 @@ import com.tangem.features.send.impl.presentation.state.* import com.tangem.features.send.impl.presentation.state.fee.FeeSelectorState import com.tangem.features.send.impl.presentation.state.fee.FeeType import com.tangem.features.send.impl.presentation.viewmodel.SendClickIntents +import com.tangem.lib.crypto.BlockchainUtils.isDogecoin import com.tangem.utils.Provider import com.tangem.utils.isNullOrZero import kotlinx.collections.immutable.ImmutableList @@ -52,18 +53,17 @@ internal class SendNotificationFactory( val feeState = state.feeState ?: return@map persistentListOf() val feeAmount = feeState.fee?.amount?.value ?: BigDecimal.ZERO val amountValue = state.amountState?.amountTextField?.cryptoAmount?.value ?: BigDecimal.ZERO - val sendAmount = if (sendState.isSubtract) amountValue.minus(feeAmount) else amountValue buildList { // errors - addExceedBalanceNotification(feeAmount, sendAmount) + addExceedBalanceNotification(feeAmount, amountValue) addExceedsBalanceNotification(feeState.fee) - addInvalidAmountNotification(sendState.isSubtract, sendAmount) - addMinimumAmountErrorNotification(feeAmount, sendAmount) - addDustWarningNotification(feeAmount, sendAmount) - addTransactionLimitErrorNotification(feeAmount, sendAmount) + addInvalidAmountNotification(sendState.isSubtract, amountValue) + addMinimumAmountErrorNotification(feeAmount, amountValue) + addDustWarningNotification(feeAmount, amountValue) + addTransactionLimitErrorNotification(feeAmount, amountValue) // warnings - addExistentialWarningNotification(feeAmount, sendAmount) - addHighFeeWarningNotification(sendAmount, sendState.ignoreAmountReduce) + addExistentialWarningNotification(feeAmount, amountValue) + addHighFeeWarningNotification(feeAmount, amountValue, sendState.ignoreAmountReduce) addTooLowNotification(feeState) }.toImmutableList() } @@ -121,20 +121,11 @@ internal class SendNotificationFactory( val totalAmount = feeAmount + receivedAmount val balance = coinCryptoCurrencyStatus.value.amount ?: BigDecimal.ZERO - // TODO Move Blockchain check elsewhere - when (coinCryptoCurrencyStatus.currency.network.id.value) { - Blockchain.Cardano.id -> { - if (receivedAmount > BigDecimal.ONE || balance - totalAmount < BigDecimal.ONE) { - add(SendNotification.Error.MinimumAmountError(CARDANO_MINIMUM)) - } + if (isDogecoin(coinCryptoCurrencyStatus.currency.network.id.value)) { + val minimum = BigDecimal(DOGECOIN_MINIMUM) + if (receivedAmount < minimum || balance - totalAmount < minimum) { + add(SendNotification.Error.MinimumAmountError(DOGECOIN_MINIMUM)) } - Blockchain.Dogecoin.id -> { - val minimum = BigDecimal(DOGECOIN_MINIMUM) - if (receivedAmount > minimum || balance - totalAmount < minimum) { - add(SendNotification.Error.MinimumAmountError(DOGECOIN_MINIMUM)) - } - } - else -> Unit } } @@ -199,7 +190,9 @@ internal class SendNotificationFactory( receivedAmount: BigDecimal, ) { val userWalletId = userWalletProvider().walletId - val cryptoCurrency = cryptoCurrencyStatusProvider().currency + val cryptoCurrencyStatus = cryptoCurrencyStatusProvider() + val cryptoCurrency = cryptoCurrencyStatus.currency + val balance = cryptoCurrencyStatus.value.amount ?: return val spendingAmount = if (cryptoCurrency is CryptoCurrency.Token) { feeAmount } else { @@ -209,7 +202,8 @@ internal class SendNotificationFactory( userWalletId, cryptoCurrency.network, ) - if (currencyDeposit != null && currencyDeposit > spendingAmount) { + val diff = balance.minus(spendingAmount) + if (currencyDeposit != null && currencyDeposit > diff) { add( SendNotification.Error.ExistentialDeposit( BigDecimalFormatter.formatCryptoAmount( @@ -222,13 +216,15 @@ internal class SendNotificationFactory( } private fun MutableList.addHighFeeWarningNotification( + feeAmount: BigDecimal, sendAmount: BigDecimal, ignoreAmountReduce: Boolean, ) { val cryptoCurrencyStatus = cryptoCurrencyStatusProvider() val balance = cryptoCurrencyStatus.value.amount ?: BigDecimal.ZERO val isTezos = cryptoCurrencyStatus.currency.network.id.value == Blockchain.Tezos.id - if (!ignoreAmountReduce && sendAmount == balance && isTezos) { + val isTotalBalance = feeAmount.plus(sendAmount) >= balance + if (!ignoreAmountReduce && isTotalBalance && isTezos) { add( SendNotification.Warning.HighFeeError( amount = TEZOS_FEE_THRESHOLD.toPlainString(), @@ -361,7 +357,6 @@ internal class SendNotificationFactory( } companion object { - private const val CARDANO_MINIMUM = "1" private const val DOGECOIN_MINIMUM = "0.01" private val TEZOS_FEE_THRESHOLD = BigDecimal("0.01") } diff --git a/libs/crypto/src/main/java/com/tangem/lib/crypto/BlockchainUtils.kt b/libs/crypto/src/main/java/com/tangem/lib/crypto/BlockchainUtils.kt index a6e7acf14d..cf10f1ce54 100644 --- a/libs/crypto/src/main/java/com/tangem/lib/crypto/BlockchainUtils.kt +++ b/libs/crypto/src/main/java/com/tangem/lib/crypto/BlockchainUtils.kt @@ -32,4 +32,9 @@ object BlockchainUtils { val blockchain = Blockchain.fromId(networkId) return blockchain == Blockchain.Bitcoin || blockchain == Blockchain.BitcoinTestnet } + + fun isDogecoin(networkId: String): Boolean { + val blockchain = Blockchain.fromId(networkId) + return blockchain == Blockchain.Dogecoin + } } \ No newline at end of file From dd9ad45d0937c431d99ca7d162439468e9e9d525 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 11 Apr 2024 14:37:24 +0100 Subject: [PATCH 32/67] Updated on 2026-08-14 --- app/src/main/assets/tangem-app-config | 2 +- .../java/com/tangem/datasource/config/ConfigManagerImpl.kt | 3 +++ .../java/com/tangem/datasource/config/models/JsonModels.kt | 3 +++ .../java/com/tangem/domain/common/extensions/Blockchain.kt | 1 - gradle/dependencies.toml | 2 +- 5 files changed, 8 insertions(+), 3 deletions(-) diff --git a/app/src/main/assets/tangem-app-config b/app/src/main/assets/tangem-app-config index 82f4a8aebc..980cc4cb2f 160000 --- a/app/src/main/assets/tangem-app-config +++ b/app/src/main/assets/tangem-app-config @@ -1 +1 @@ -Subproject commit 82f4a8aebcab924047705ad3ecd5f008f07ca0b5 +Subproject commit 980cc4cb2fb9ba44cd6798191ae7d208e4f7f74c diff --git a/core/datasource/src/main/java/com/tangem/datasource/config/ConfigManagerImpl.kt b/core/datasource/src/main/java/com/tangem/datasource/config/ConfigManagerImpl.kt index 8ebac44c3b..d26cc5e36e 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/config/ConfigManagerImpl.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/config/ConfigManagerImpl.kt @@ -146,6 +146,9 @@ internal class ConfigManagerImpl @Inject constructor() : ConfigManager { blockBookRest = accessTokens.bitcoin?.blockBookRest, ), algorand = GetBlockAccessToken(rest = accessTokens.algorand?.rest), + zkSync = GetBlockAccessToken(rest = accessTokens.zksync?.rest), + polygonZkevm = GetBlockAccessToken(rest = accessTokens.polygonZkevm?.rest), + base = GetBlockAccessToken(rest = accessTokens.base?.rest), ) } } diff --git a/core/datasource/src/main/java/com/tangem/datasource/config/models/JsonModels.kt b/core/datasource/src/main/java/com/tangem/datasource/config/models/JsonModels.kt index 0e572bd3e5..94987bf391 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/config/models/JsonModels.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/config/models/JsonModels.kt @@ -67,6 +67,9 @@ data class GetBlockAccessTokens( @Json(name = "dash") val dash: GetBlockToken?, @Json(name = "bitcoin") val bitcoin: GetBlockToken?, @Json(name = "algorand") val algorand: GetBlockToken?, + @Json(name = "polygon-zkevm") val polygonZkevm: GetBlockToken?, + @Json(name = "zksync") val zksync: GetBlockToken?, + @Json(name = "base") val base: GetBlockToken?, ) @JsonClass(generateAdapter = true) diff --git a/domain/legacy/src/main/java/com/tangem/domain/common/extensions/Blockchain.kt b/domain/legacy/src/main/java/com/tangem/domain/common/extensions/Blockchain.kt index ff110169a6..36caa85c7f 100644 --- a/domain/legacy/src/main/java/com/tangem/domain/common/extensions/Blockchain.kt +++ b/domain/legacy/src/main/java/com/tangem/domain/common/extensions/Blockchain.kt @@ -328,7 +328,6 @@ private const val NODL_AMOUNT_TO_CREATE_ACCOUNT = 1.5 private val excludedBlockchains = listOf( Blockchain.Unknown, - Blockchain.Playa3ull, Blockchain.Nexa, Blockchain.NexaTestnet, Blockchain.Radiant, diff --git a/gradle/dependencies.toml b/gradle/dependencies.toml index 4995c29d6a..8fe2844437 100644 --- a/gradle/dependencies.toml +++ b/gradle/dependencies.toml @@ -85,7 +85,7 @@ leakcanary = "2.13" # endregion Other libraries # region Tangem -tangemBlockchainSdk = "release-app_5.9-582" +tangemBlockchainSdk = "release-app_5.9-589" #tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds tangemCardSdk = "release-app_5.9-343" #tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^ From 8735392aff769462a08c3a42c44a19d1d2084d35 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 11 Apr 2024 18:01:31 +0500 Subject: [PATCH 33/67] Updated on 2026-08-14 --- .../send/impl/presentation/ui/SendScreen.kt | 2 +- .../ui/fee/SendSpeedSelectorItem.kt | 34 ++----------------- .../impl/presentation/ui/send/FeeBlock.kt | 18 +++------- .../impl/presentation/utils/FormatterUtils.kt | 34 +++++++++++++++++++ 4 files changed, 41 insertions(+), 47 deletions(-) create mode 100644 features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/utils/FormatterUtils.kt diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/SendScreen.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/SendScreen.kt index 55bebe81a7..2dcff9830c 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/SendScreen.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/SendScreen.kt @@ -33,6 +33,7 @@ internal fun SendScreen(uiState: SendUiState, currentState: SendUiCurrentScreen) Column( modifier = Modifier .fillMaxSize() + .imePadding() .systemBarsPadding() .background(color = TangemTheme.colors.background.tertiary), horizontalAlignment = Alignment.CenterHorizontally, @@ -78,7 +79,6 @@ internal fun SendScreen(uiState: SendUiState, currentState: SendUiCurrentScreen) SendNavigationButtons( uiState = uiState, currentState = currentState, - modifier = Modifier.imePadding(), ) } diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/fee/SendSpeedSelectorItem.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/fee/SendSpeedSelectorItem.kt index 3ecc6ddf2e..bb360d218b 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/fee/SendSpeedSelectorItem.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/fee/SendSpeedSelectorItem.kt @@ -17,18 +17,15 @@ import com.tangem.blockchain.common.Amount import com.tangem.blockchain.common.transaction.TransactionFee import com.tangem.core.ui.components.SpacerWMax import com.tangem.core.ui.components.rows.SelectorRowItem -import com.tangem.core.ui.extensions.TextReference -import com.tangem.core.ui.extensions.combinedReference -import com.tangem.core.ui.extensions.stringReference import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.utils.BigDecimalFormatter -import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.features.send.impl.R import com.tangem.features.send.impl.presentation.state.SendNotification import com.tangem.features.send.impl.presentation.state.SendStates import com.tangem.features.send.impl.presentation.state.fee.FeeSelectorState import com.tangem.features.send.impl.presentation.state.fee.FeeType -import java.math.BigDecimal +import com.tangem.features.send.impl.presentation.utils.getCryptoReference +import com.tangem.features.send.impl.presentation.utils.getFiatReference @Composable internal fun SendSpeedSelectorItem( @@ -76,33 +73,6 @@ internal fun SendSpeedSelectorItem( } } -// todo remove after refactoring [REDACTED_JIRA] -private fun getCryptoReference(amount: Amount?, isFeeApproximate: Boolean): TextReference? { - if (amount == null) return null - return combinedReference( - if (isFeeApproximate) stringReference("${BigDecimalFormatter.CAN_BE_LOWER_SIGN} ") else TextReference.EMPTY, - stringReference( - BigDecimalFormatter.formatCryptoAmount( - cryptoAmount = amount.value, - cryptoCurrency = amount.currencySymbol, - decimals = amount.decimals, - ), - ), - ) -} - -// todo remove after refactoring [REDACTED_JIRA] -private fun getFiatReference(amount: Amount?, rate: BigDecimal?, appCurrency: AppCurrency): TextReference? { - if (amount == null) return null - return stringReference( - BigDecimalFormatter.formatFiatAmount( - fiatAmount = rate?.let { amount.value?.multiply(it) }, - fiatCurrencyCode = appCurrency.code, - fiatCurrencySymbol = appCurrency.symbol, - ), - ) -} - @Composable private fun SendSpeedSelectorItemError(isError: Boolean) { Row { diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/send/FeeBlock.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/send/FeeBlock.kt index 23af8a4893..a3ee42ccbb 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/send/FeeBlock.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/send/FeeBlock.kt @@ -15,14 +15,14 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.PreviewParameterProvider import com.tangem.core.ui.components.rows.SelectorRowItem -import com.tangem.core.ui.extensions.stringReference import com.tangem.core.ui.res.TangemTheme -import com.tangem.core.ui.utils.BigDecimalFormatter import com.tangem.features.send.impl.R import com.tangem.features.send.impl.presentation.state.SendStates import com.tangem.features.send.impl.presentation.state.fee.FeeSelectorState import com.tangem.features.send.impl.presentation.state.fee.FeeType import com.tangem.features.send.impl.presentation.state.previewdata.FeeStatePreviewData +import com.tangem.features.send.impl.presentation.utils.getCryptoReference +import com.tangem.features.send.impl.presentation.utils.getFiatReference @Composable internal fun FeeBlock(feeState: SendStates.FeeState, isSuccess: Boolean, onClick: () -> Unit) { @@ -34,16 +34,6 @@ internal fun FeeBlock(feeState: SendStates.FeeState, isSuccess: Boolean, onClick FeeType.Fast -> R.string.common_fee_selector_option_fast to R.drawable.ic_hare_24 FeeType.Custom -> R.string.common_fee_selector_option_custom to R.drawable.ic_edit_24 } - val feeCryptoValue = BigDecimalFormatter.formatCryptoAmount( - cryptoAmount = fee.amount.value, - cryptoCurrency = fee.amount.currencySymbol, - decimals = fee.amount.decimals, - ) - val feeFiatValue = BigDecimalFormatter.formatFiatAmount( - fiatAmount = feeState.rate?.let { fee.amount.value?.multiply(it) }, - fiatCurrencyCode = feeState.appCurrency.code, - fiatCurrencySymbol = feeState.appCurrency.symbol, - ) Column( modifier = Modifier .fillMaxWidth() @@ -60,8 +50,8 @@ internal fun FeeBlock(feeState: SendStates.FeeState, isSuccess: Boolean, onClick SelectorRowItem( titleRes = title, iconRes = icon, - preDot = stringReference(feeCryptoValue), - postDot = stringReference(feeFiatValue), + preDot = getCryptoReference(fee.amount, feeState.isFeeApproximate), + postDot = getFiatReference(fee.amount, feeState.rate, feeState.appCurrency), ellipsizeOffset = fee.amount.currencySymbol.length, isSelected = true, showDivider = false, diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/utils/FormatterUtils.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/utils/FormatterUtils.kt new file mode 100644 index 0000000000..022d0c5711 --- /dev/null +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/utils/FormatterUtils.kt @@ -0,0 +1,34 @@ +package com.tangem.features.send.impl.presentation.utils + +import com.tangem.blockchain.common.Amount +import com.tangem.core.ui.extensions.TextReference +import com.tangem.core.ui.extensions.combinedReference +import com.tangem.core.ui.extensions.stringReference +import com.tangem.core.ui.utils.BigDecimalFormatter +import com.tangem.domain.appcurrency.model.AppCurrency +import java.math.BigDecimal + +internal fun getCryptoReference(amount: Amount?, isFeeApproximate: Boolean): TextReference? { + if (amount == null) return null + return combinedReference( + if (isFeeApproximate) stringReference("${BigDecimalFormatter.CAN_BE_LOWER_SIGN} ") else TextReference.EMPTY, + stringReference( + BigDecimalFormatter.formatCryptoAmount( + cryptoAmount = amount.value, + cryptoCurrency = amount.currencySymbol, + decimals = amount.decimals, + ), + ), + ) +} + +internal fun getFiatReference(amount: Amount?, rate: BigDecimal?, appCurrency: AppCurrency): TextReference? { + if (amount == null) return null + return stringReference( + BigDecimalFormatter.formatFiatAmount( + fiatAmount = rate?.let { amount.value?.multiply(it) }, + fiatCurrencyCode = appCurrency.code, + fiatCurrencySymbol = appCurrency.symbol, + ), + ) +} \ No newline at end of file From badcca6fb9c147ad5820f77e0f5d2c7f0b6051b4 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 11 Apr 2024 18:02:08 +0500 Subject: [PATCH 34/67] Updated on 2026-08-14 --- .../fee/custom/BitcoinCustomFeeConverter.kt | 70 ++++++++++--------- 1 file changed, 38 insertions(+), 32 deletions(-) diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/custom/BitcoinCustomFeeConverter.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/custom/BitcoinCustomFeeConverter.kt index 6fb6ce20ef..e42f25a9d5 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/custom/BitcoinCustomFeeConverter.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/custom/BitcoinCustomFeeConverter.kt @@ -17,6 +17,7 @@ import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.features.send.impl.R import com.tangem.features.send.impl.presentation.state.fields.SendTextField import com.tangem.features.send.impl.presentation.viewmodel.SendClickIntents +import com.tangem.lib.crypto.BlockchainUtils.isBitcoin import com.tangem.utils.Provider import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf @@ -32,40 +33,45 @@ internal class BitcoinCustomFeeConverter( override fun convert(value: Fee.Bitcoin): ImmutableList { val feeValue = value.amount.value - return persistentListOf( - SendTextField.CustomFee( - value = feeValue?.parseBigDecimal(value.amount.decimals).orEmpty(), - decimals = value.amount.decimals, - symbol = value.amount.currencySymbol, - onValueChange = { clickIntents.onCustomFeeValueChange(FEE_AMOUNT_INDEX, it) }, - keyboardOptions = KeyboardOptions( - imeAction = ImeAction.Next, - keyboardType = KeyboardType.Number, - ), - title = resourceReference(R.string.send_max_fee), - footer = resourceReference(R.string.send_max_fee_footer), - label = getFeeFormatted(feeValue), - keyboardActions = KeyboardActions(), - isReadonly = true, - ), - SendTextField.CustomFee( - value = toSatoshiPerByte( - amount = feeValue, + val network = feeCryptoCurrencyStatusProvider()?.currency?.network?.id?.value + return if (network != null && isBitcoin(network)) { + persistentListOf( + SendTextField.CustomFee( + value = feeValue?.parseBigDecimal(value.amount.decimals).orEmpty(), decimals = value.amount.decimals, - txSize = value.txSize, - ).toString(), - decimals = SATOSHI_DECIMALS, - symbol = "", - title = resourceReference(R.string.send_gas_price), - footer = resourceReference(R.string.send_gas_price_footer), - onValueChange = { clickIntents.onCustomFeeValueChange(FEE_SATOSHI_INDEX, it) }, - keyboardOptions = KeyboardOptions( - imeAction = if (checkExceedBalance(feeValue)) ImeAction.None else ImeAction.Done, - keyboardType = KeyboardType.Number, + symbol = value.amount.currencySymbol, + onValueChange = { clickIntents.onCustomFeeValueChange(FEE_AMOUNT_INDEX, it) }, + keyboardOptions = KeyboardOptions( + imeAction = ImeAction.Next, + keyboardType = KeyboardType.Number, + ), + title = resourceReference(R.string.send_max_fee), + footer = resourceReference(R.string.send_max_fee_footer), + label = getFeeFormatted(feeValue), + keyboardActions = KeyboardActions(), + isReadonly = true, ), - keyboardActions = KeyboardActions(), - ), - ) + SendTextField.CustomFee( + value = toSatoshiPerByte( + amount = feeValue, + decimals = value.amount.decimals, + txSize = value.txSize, + ).toString(), + decimals = SATOSHI_DECIMALS, + symbol = "", + title = resourceReference(R.string.send_gas_price), + footer = resourceReference(R.string.send_gas_price_footer), + onValueChange = { clickIntents.onCustomFeeValueChange(FEE_SATOSHI_INDEX, it) }, + keyboardOptions = KeyboardOptions( + imeAction = if (checkExceedBalance(feeValue)) ImeAction.None else ImeAction.Done, + keyboardType = KeyboardType.Number, + ), + keyboardActions = KeyboardActions(), + ), + ) + } else { + persistentListOf() + } } override fun convertBack(normalFee: Fee.Bitcoin, value: ImmutableList): Fee.Bitcoin { From 1264ada90d1f8dfe96c1c848c17a928826e5b83f Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 11 Apr 2024 18:02:49 +0500 Subject: [PATCH 35/67] Updated on 2026-08-14 --- .../impl/presentation/state/SendEventStateFactory.kt | 7 ++++++- .../state/SendTransactionAlertConverter.kt | 11 ++++++++++- .../send/impl/presentation/viewmodel/SendViewModel.kt | 2 +- 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendEventStateFactory.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendEventStateFactory.kt index 58e0a97b65..3e615c7835 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendEventStateFactory.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendEventStateFactory.kt @@ -4,6 +4,7 @@ import com.tangem.blockchain.common.transaction.TransactionFee import com.tangem.core.ui.event.consumedEvent import com.tangem.core.ui.event.triggeredEvent import com.tangem.core.ui.utils.BigDecimalFormatter +import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.domain.transaction.error.SendTransactionError import com.tangem.features.send.impl.presentation.state.fee.FeeSelectorState import com.tangem.features.send.impl.presentation.state.fee.FeeStateFactory @@ -21,11 +22,15 @@ import java.math.BigDecimal */ internal class SendEventStateFactory( private val currentStateProvider: Provider, + private val cryptoCurrencyStatusProvider: Provider, private val clickIntents: SendClickIntents, private val feeStateFactory: FeeStateFactory, ) { private val sendTransactionErrorConverter by lazy(LazyThreadSafetyMode.NONE) { - SendTransactionAlertConverter(clickIntents) + SendTransactionAlertConverter( + cryptoCurrencyStatusProvider = cryptoCurrencyStatusProvider, + clickIntents = clickIntents, + ) } fun onConsumeEventState(): SendUiState { diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendTransactionAlertConverter.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendTransactionAlertConverter.kt index ad5d16e175..7f6a8588b1 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendTransactionAlertConverter.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendTransactionAlertConverter.kt @@ -1,10 +1,14 @@ package com.tangem.features.send.impl.presentation.state +import com.tangem.core.ui.utils.BigDecimalFormatter +import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.domain.transaction.error.SendTransactionError import com.tangem.features.send.impl.presentation.viewmodel.SendClickIntents +import com.tangem.utils.Provider import com.tangem.utils.converter.Converter internal class SendTransactionAlertConverter( + private val cryptoCurrencyStatusProvider: Provider, private val clickIntents: SendClickIntents, ) : Converter { override fun convert(value: SendTransactionError): SendAlertState? { @@ -38,7 +42,12 @@ internal class SendTransactionAlertConverter( cause = value.ex?.localizedMessage, onConfirmClick = { clickIntents.onFailedTxEmailClick(value.ex?.localizedMessage.orEmpty()) }, ) - is SendTransactionError.CreateAccountUnderfunded -> SendAlertState.ReserveAmount(value.amount) + is SendTransactionError.CreateAccountUnderfunded -> SendAlertState.ReserveAmount( + BigDecimalFormatter.formatWithSymbol( + amount = value.amount, + symbol = cryptoCurrencyStatusProvider().currency.symbol, + ), + ) else -> null } } diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendViewModel.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendViewModel.kt index 99e752dd9f..1533c95e7c 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendViewModel.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendViewModel.kt @@ -143,6 +143,7 @@ internal class SendViewModel @Inject constructor( private val eventStateFactory = SendEventStateFactory( clickIntents = this, currentStateProvider = Provider { uiState }, + cryptoCurrencyStatusProvider = Provider { cryptoCurrencyStatus }, feeStateFactory = feeStateFactory, ) @@ -784,7 +785,6 @@ internal class SendViewModel @Inject constructor( override fun onAmountReduceClick(reducedAmount: String, clazz: Class) { uiState = amountStateFactory.getOnAmountValueChange(reducedAmount) uiState = sendNotificationFactory.dismissNotificationState(clazz) - onCheckFeeUpdate() } override fun onNotificationCancel(clazz: Class) { From 08ddee9accccfb85a27832cbdac65a065bc44f5e Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 11 Apr 2024 19:59:05 +0500 Subject: [PATCH 36/67] Updated on 2026-08-14 --- core/res/src/main/res/values-ru/strings.xml | 10 ++--- core/res/src/main/res/values/strings.xml | 4 +- .../impl/presentation/state/SendAlertState.kt | 10 +++++ .../state/SendEventStateFactory.kt | 14 +++++++ .../state/confirm/SendNotificationFactory.kt | 15 ++++++++ .../presentation/state/fee/FeeCalculation.kt | 16 ++++++++ .../state/fee/FeeNotificationFactory.kt | 38 +------------------ .../ui/fee/SendSpeedSelectorItem.kt | 32 ---------------- .../presentation/viewmodel/SendViewModel.kt | 10 ++++- 9 files changed, 73 insertions(+), 76 deletions(-) diff --git a/core/res/src/main/res/values-ru/strings.xml b/core/res/src/main/res/values-ru/strings.xml index 136cc2857f..256b94877c 100644 --- a/core/res/src/main/res/values-ru/strings.xml +++ b/core/res/src/main/res/values-ru/strings.xml @@ -79,7 +79,7 @@ Посмотреть историю транзакций Обозреватель Комиссия - Сетевые комиссии – это плата пользователя за обработку и подтверждение транзакций. Размер комиссии зависит от нагрузки на сеть, объема транзакции и приоритета исполнения. %s + Сетевые комиссии – это плата пользователя за обработку и подтверждение транзакций. Размер комиссии зависит от нагрузки на сеть, объема транзакции и приоритета исполнения. %s Свое Быстро По рынку @@ -310,7 +310,7 @@ В этом случае вам будет необходимо начать процесс заново. Вы хотите выйти из процесса активации? Подготовка - Другой кошелек уже был создан на карте, которую вы пытаетесь добавить. Хотите сбросить его и использовать карту для бэкапа? + Другой кошелек уже был создан на карте, которую вы пытаетесь добавить. Если на нем есть средства, пожалуйста сначала выведите их, а затем сделайте сброс до заводских настроек и используйте как резервную. Резервная копия Прочитать о seed-фразе @@ -426,12 +426,12 @@ Приготовьте свою карту Уже содержится в введенном адресе Вычесть - Недостаточно средств для покрытия комиссии сети. Вычесть комиссию %s из отправляемой сумму? + Недостаточно средств для покрытия комиссии сети. Вычесть комиссию %s из отправляемой суммы? + Сумма комиссии в %s раз превышает рекомендованную. Убедитесь, что указанная комиссия верна. Вы указали комиссию ниже рекомендуемой, это может привести к задержке исполнения вашей транзакции. Продолжить? Причина: %1$s\nКод: %2$s Транзакция не выполнена Сумма - Подтверждение %1$s, %2$s Адрес Код назначения @@ -536,7 +536,7 @@ Балансы скрыты Балансы показаны Отменить - В данный момент покупка монеты %s недоступна. Но мы работаем над её добавлением.” + В данный момент покупка монеты %s недоступна. Но мы работаем над её добавлением. У вас нет средств для отправки. Пополните счет, чтобы иметь возможность отправить с него средства. Выбранная операция в данный момент недоступна. Попробуйте позже. Обмен %s не доступен. Но мы работаем над его добавлением. diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml index eacae3a8ac..a68246f965 100644 --- a/core/res/src/main/res/values/strings.xml +++ b/core/res/src/main/res/values/strings.xml @@ -310,7 +310,7 @@ In this case, you will need to start from the beginning. Do you want to exit the activation process? Getting started - Another wallet has already been created on the card you\'re trying to add. Do you want to reset it and use the card for a new wallet? + Another wallet has already been created on the card you\'re trying to add. If you have funds in this wallet, please withdraw it and then reset this card and add it as a backup. Creating a backup Read more about seed phrase @@ -422,11 +422,11 @@ Already included in the entered address Subtract Not enough funds to cover the network commission. Subtract the commission %s from the amount sent? + The commission amount is %s times the recommended amount. Make sure that the custom settings are correct. You specified a commission below the recommended amount, which could cause a delay in your transaction. Continue? Reason: %1$s\nCode: %2$s The transaction is not completed Amount - Confirm %1$s, %2$s Address Destination Tag diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendAlertState.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendAlertState.kt index d3adc2e74c..b8da006bb9 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendAlertState.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendAlertState.kt @@ -58,6 +58,16 @@ internal sealed class SendAlertState { override val confirmButtonText: TextReference = resourceReference(R.string.common_continue) } + data class FeeTooHigh( + val times: String, + override val onConfirmClick: () -> Unit, + ) : SendAlertState() { + override val title: TextReference? = null + override val message: TextReference = + resourceReference(id = R.string.send_alert_fee_too_high_text, wrappedList(times)) + override val confirmButtonText: TextReference = resourceReference(R.string.common_continue) + } + data class FeeCoverage( val amount: String, override val onConfirmClick: (() -> Unit), diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendEventStateFactory.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendEventStateFactory.kt index 3e615c7835..1798d8fe79 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendEventStateFactory.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendEventStateFactory.kt @@ -115,6 +115,20 @@ internal class SendEventStateFactory( ) } + fun getFeeTooHighAlert(diff: String, onConsume: () -> Unit): SendUiState { + return currentStateProvider().copy( + event = triggeredEvent( + data = SendEvent.ShowAlert( + SendAlertState.FeeTooHigh( + onConfirmClick = clickIntents::showSend, + times = diff, + ), + ), + onConsume = onConsume, + ), + ) + } + fun getGenericErrorState(error: Throwable? = null, onConsume: () -> Unit): SendUiState { val state = currentStateProvider() return state.copy( diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/confirm/SendNotificationFactory.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/confirm/SendNotificationFactory.kt index 709779d1df..6a4e8a383e 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/confirm/SendNotificationFactory.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/confirm/SendNotificationFactory.kt @@ -30,6 +30,7 @@ import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.map import java.math.BigDecimal +import java.math.BigInteger @Suppress("LongParameterList") internal class SendNotificationFactory( @@ -64,6 +65,7 @@ internal class SendNotificationFactory( // warnings addExistentialWarningNotification(feeAmount, amountValue) addHighFeeWarningNotification(feeAmount, amountValue, sendState.ignoreAmountReduce) + addTooHighNotification(feeState.feeSelectorState) addTooLowNotification(feeState) }.toImmutableList() } @@ -279,6 +281,18 @@ internal class SendNotificationFactory( } } + private fun MutableList.addTooHighNotification(feeSelectorState: FeeSelectorState) { + if (feeSelectorState !is FeeSelectorState.Content) return + val multipleFees = feeSelectorState.fees as? TransactionFee.Choosable ?: return + val highValue = multipleFees.priority.amount.value ?: return + val customAmount = feeSelectorState.customValues.firstOrNull() ?: return + val customValue = customAmount.value.parseToBigDecimal(customAmount.decimals) + val diff = (customValue / highValue).toBigInteger() + if (feeSelectorState.selectedFee == FeeType.Custom && diff > FEE_MAX_DIFF) { + add(SendNotification.Warning.TooHigh(diff.toString())) + } + } + private suspend fun MutableList.addExceedsBalanceNotification(fee: Fee?) { val feeValue = fee?.amount?.value ?: BigDecimal.ZERO val userWalletId = userWalletProvider().walletId @@ -359,5 +373,6 @@ internal class SendNotificationFactory( companion object { private const val DOGECOIN_MINIMUM = "0.01" private val TEZOS_FEE_THRESHOLD = BigDecimal("0.01") + internal val FEE_MAX_DIFF = BigInteger("5") } } \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeCalculation.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeCalculation.kt index 1a57669334..06a47de259 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeCalculation.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeCalculation.kt @@ -4,6 +4,7 @@ import com.tangem.blockchain.common.transaction.TransactionFee import com.tangem.core.ui.utils.parseToBigDecimal import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.features.send.impl.presentation.state.SendUiState +import com.tangem.features.send.impl.presentation.state.confirm.SendNotificationFactory /** * Check if sending amount with fee is greater than balance @@ -26,4 +27,19 @@ internal fun checkIfFeeTooLow(state: SendUiState): Boolean { val customValue = customAmount.value.parseToBigDecimal(customAmount.decimals) return feeSelectorState.selectedFee == FeeType.Custom && minimumValue > customValue +} + +/** + * Check if custom fee is too high + */ +internal fun checkIfFeeTooHigh(state: SendUiState, onShow: (String) -> Unit): Boolean { + val feeSelectorState = state.feeState?.feeSelectorState as? FeeSelectorState.Content ?: return false + val multipleFees = feeSelectorState.fees as? TransactionFee.Choosable ?: return false + val highValue = multipleFees.priority.amount.value ?: return false + val customAmount = feeSelectorState.customValues.firstOrNull() ?: return false + val customValue = customAmount.value.parseToBigDecimal(customAmount.decimals) + val diff = (customValue / highValue).toBigInteger() + val isShow = feeSelectorState.selectedFee == FeeType.Custom && diff > SendNotificationFactory.FEE_MAX_DIFF + if (isShow) onShow(diff.toString()) + return isShow } \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeNotificationFactory.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeNotificationFactory.kt index 217631c38e..92189a24ef 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeNotificationFactory.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeNotificationFactory.kt @@ -1,20 +1,15 @@ package com.tangem.features.send.impl.presentation.state.fee -import com.tangem.blockchain.common.transaction.TransactionFee -import com.tangem.core.ui.utils.parseToBigDecimal +import com.tangem.features.send.impl.presentation.state.SendNotification import com.tangem.features.send.impl.presentation.state.SendUiState import com.tangem.features.send.impl.presentation.state.SendUiStateType import com.tangem.features.send.impl.presentation.state.StateRouter -import com.tangem.features.send.impl.presentation.state.fields.SendTextField -import com.tangem.features.send.impl.presentation.state.SendNotification import com.tangem.features.send.impl.presentation.viewmodel.SendClickIntents import com.tangem.utils.Provider -import com.tangem.utils.toFormattedString import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toImmutableList import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.map -import java.math.BigDecimal @Suppress("LongParameterList") internal class FeeNotificationFactory( @@ -29,16 +24,7 @@ internal class FeeNotificationFactory( val state = currentStateProvider() val feeState = state.feeState ?: return@map persistentListOf() buildList { - when (val feeSelectorState = feeState.feeSelectorState) { - FeeSelectorState.Error -> { - addFeeUnreachableNotification(feeSelectorState) - } - is FeeSelectorState.Content -> { - val customFee = feeSelectorState.customValues - val selectedFee = feeSelectorState.selectedFee - addTooHighNotification(feeSelectorState.fees, selectedFee, customFee) - } - } + addFeeUnreachableNotification(feeState.feeSelectorState) }.toImmutableList() } @@ -47,24 +33,4 @@ internal class FeeNotificationFactory( add(SendNotification.Warning.NetworkFeeUnreachable(clickIntents::feeReload)) } } - - private fun MutableList.addTooHighNotification( - transactionFee: TransactionFee, - selectedFee: FeeType, - customFee: List, - ) { - val multipleFees = transactionFee as? TransactionFee.Choosable ?: return - val highValue = multipleFees.priority.amount.value ?: return - val customAmount = customFee.firstOrNull() ?: return - val customValue = customAmount.value.parseToBigDecimal(customAmount.decimals) - val diff = customValue / highValue - if (selectedFee == FeeType.Custom && diff > FEE_MAX_DIFF) { - add(SendNotification.Warning.TooHigh(diff.toFormattedString(HIGH_FEE_DIFF_DECIMALS))) - } - } - - companion object { - private val FEE_MAX_DIFF = BigDecimal(5) - private const val HIGH_FEE_DIFF_DECIMALS = 0 - } } \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/fee/SendSpeedSelectorItem.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/fee/SendSpeedSelectorItem.kt index bb360d218b..0a2a65c93e 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/fee/SendSpeedSelectorItem.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/fee/SendSpeedSelectorItem.kt @@ -3,7 +3,6 @@ package com.tangem.features.send.impl.presentation.ui.fee import androidx.annotation.DrawableRes import androidx.annotation.StringRes import androidx.compose.animation.* -import androidx.compose.foundation.Image import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Row @@ -12,15 +11,12 @@ import androidx.compose.foundation.layout.padding import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier -import androidx.compose.ui.res.painterResource import com.tangem.blockchain.common.Amount import com.tangem.blockchain.common.transaction.TransactionFee import com.tangem.core.ui.components.SpacerWMax import com.tangem.core.ui.components.rows.SelectorRowItem import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.utils.BigDecimalFormatter -import com.tangem.features.send.impl.R -import com.tangem.features.send.impl.presentation.state.SendNotification import com.tangem.features.send.impl.presentation.state.SendStates import com.tangem.features.send.impl.presentation.state.fee.FeeSelectorState import com.tangem.features.send.impl.presentation.state.fee.FeeType @@ -64,11 +60,6 @@ internal fun SendSpeedSelectorItem( showDivider = showDivider, ) SendSpeedSelectorItemError(isError = feeSelectorState is FeeSelectorState.Error) - - if (feeType == FeeType.Custom) { - val showWarning = state.notifications.any { it is SendNotification.Warning.TooHigh } - WarningIcon(showWarning = showWarning) - } } } } @@ -97,29 +88,6 @@ private fun SendSpeedSelectorItemError(isError: Boolean) { } } -@Composable -private fun WarningIcon(showWarning: Boolean = false) { - Row { - SpacerWMax() - AnimatedVisibility( - visible = showWarning, - label = "Custom fee warning indicator", - enter = fadeIn(), - exit = fadeOut(), - ) { - Image( - painter = painterResource(R.drawable.ic_alert_triangle_20), - contentDescription = null, - modifier = Modifier - .padding( - vertical = TangemTheme.dimens.spacing12, - horizontal = TangemTheme.dimens.spacing14, - ), - ) - } - } -} - private fun FeeSelectorState.Content.getAmount(feeType: FeeType): Amount? { val choosableFees = fees as? TransactionFee.Choosable return when (feeType) { diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendViewModel.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendViewModel.kt index 1533c95e7c..8564920606 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendViewModel.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendViewModel.kt @@ -525,7 +525,15 @@ internal class SendViewModel @Inject constructor( ) return true } - return false + return checkIfFeeTooHigh( + state = uiState, + onShow = { diff -> + uiState = eventStateFactory.getFeeTooHighAlert( + diff = diff, + onConsume = { uiState = eventStateFactory.onConsumeEventState() }, + ) + }, + ) } private fun onFeeCoverageAlert(): Boolean { From ff486475ea2071a21b8f79948147af4e3318fe74 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 12 Apr 2024 12:48:55 +0500 Subject: [PATCH 37/67] Updated on 2026-08-14 --- core/res/src/main/res/values-ru/strings.xml | 5 +++-- core/res/src/main/res/values/strings.xml | 6 ++++-- .../send/impl/presentation/state/SendAlertState.kt | 3 +-- .../impl/presentation/state/SendEventStateFactory.kt | 11 +---------- .../send/impl/presentation/state/SendUiState.kt | 1 + .../state/amount/SendAmountStateConverter.kt | 1 + .../state/amount/SendAmountSubtractConverter.kt | 7 +++++-- .../impl/presentation/state/fee/FeeCalculation.kt | 2 +- .../state/fee/custom/BitcoinCustomFeeConverter.kt | 4 ++-- .../state/fields/SendAmountFieldChangeConverter.kt | 1 + .../state/fields/SendAmountFieldMaxAmountConverter.kt | 1 + .../state/previewdata/AmountStatePreviewData.kt | 1 + 12 files changed, 22 insertions(+), 21 deletions(-) diff --git a/core/res/src/main/res/values-ru/strings.xml b/core/res/src/main/res/values-ru/strings.xml index 256b94877c..b4bd309592 100644 --- a/core/res/src/main/res/values-ru/strings.xml +++ b/core/res/src/main/res/values-ru/strings.xml @@ -426,8 +426,8 @@ Приготовьте свою карту Уже содержится в введенном адресе Вычесть - Недостаточно средств для покрытия комиссии сети. Вычесть комиссию %s из отправляемой суммы? - Сумма комиссии в %s раз превышает рекомендованную. Убедитесь, что указанная комиссия верна. + Недостаточно средств для покрытия комиссии сети. Вычесть недостающую сумму для покрытия комиссии из отправляемой суммы? + Сумма комиссии в %@ раз превышает рекомендованную. Убедитесь, что указанная комиссия верна. Вы указали комиссию ниже рекомендуемой, это может привести к задержке исполнения вашей транзакции. Продолжить? Причина: %1$s\nКод: %2$s Транзакция не выполнена @@ -487,6 +487,7 @@ Отправить Мемо/ Код назначения - это код, разделяющий транзакции к общему получателю в сети криптовалют. Внимание: отсутствие мемо может привести к потере средств. Мои кошельки + Это способ измерения комиссии за отправку биткоин-транзакции. Он указывает на количество самой маленькой единицы биткоина (сатоши) за каждый байт данных в транзакции. Чем выше это число, тем быстрее будет обработана транзакция сетью. Отправка Нажмите на любое поле, чтобы изменить его Отправка %s diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml index a68246f965..a92316eb37 100644 --- a/core/res/src/main/res/values/strings.xml +++ b/core/res/src/main/res/values/strings.xml @@ -421,8 +421,8 @@ Get your card ready! Already included in the entered address Subtract - Not enough funds to cover the network commission. Subtract the commission %s from the amount sent? - The commission amount is %s times the recommended amount. Make sure that the custom settings are correct. + Not enough funds to cover the network fee. Do you want to subtract the amount required to cover the fee? + The commission amount is %@ times the recommended amount. Make sure that the custom settings are correct. You specified a commission below the recommended amount, which could cause a delay in your transaction. Continue? Reason: %1$s\nCode: %2$s The transaction is not completed @@ -482,6 +482,8 @@ Send to A Memo/Destination Tag is a unique ID for differentiating transactions sent to the same recipient on the same network. Caution: Omitting a memo may lead to misplaced funds My wallets + Это способ измерения комиссии за отправку биткоин-транзакции. Он указывает на количество самой маленькой единицы биткоина (сатоши) за каждый байт данных в транзакции. Чем выше это число, тем быстрее будет обработана транзакция сетью. + Satoshi per vbyte Sending... Tap any field to change it Send %s diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendAlertState.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendAlertState.kt index b8da006bb9..6de841373c 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendAlertState.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendAlertState.kt @@ -69,12 +69,11 @@ internal sealed class SendAlertState { } data class FeeCoverage( - val amount: String, override val onConfirmClick: (() -> Unit), ) : SendAlertState() { override val title: TextReference? = null override val message: TextReference = - resourceReference(id = R.string.send_alert_fee_coverage_title, wrappedList(amount)) + resourceReference(id = R.string.send_alert_fee_coverage_title) override val confirmButtonText: TextReference = resourceReference(id = R.string.send_alert_fee_coverage_subract_text) } diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendEventStateFactory.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendEventStateFactory.kt index 1798d8fe79..d99b6bd0ff 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendEventStateFactory.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendEventStateFactory.kt @@ -3,7 +3,6 @@ package com.tangem.features.send.impl.presentation.state import com.tangem.blockchain.common.transaction.TransactionFee import com.tangem.core.ui.event.consumedEvent import com.tangem.core.ui.event.triggeredEvent -import com.tangem.core.ui.utils.BigDecimalFormatter import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.domain.transaction.error.SendTransactionError import com.tangem.features.send.impl.presentation.state.fee.FeeSelectorState @@ -50,18 +49,10 @@ internal class SendEventStateFactory( } fun getFeeCoverageAlert(onConsume: () -> Unit): SendUiState { - val state = currentStateProvider() - val amount = state.feeState?.fee?.amount ?: return state - val feeAmount = BigDecimalFormatter.formatCryptoAmount( - cryptoAmount = amount.value, - cryptoCurrency = state.cryptoCurrencySymbol, - decimals = amount.decimals, - ) - return state.copy( + return currentStateProvider().copy( event = triggeredEvent( data = SendEvent.ShowAlert( SendAlertState.FeeCoverage( - amount = feeAmount, onConfirmClick = clickIntents::onSubtractSelect, ), ), diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendUiState.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendUiState.kt index c8e1d05950..e49450d38b 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendUiState.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendUiState.kt @@ -52,6 +52,7 @@ internal sealed class SendStates { val notifications: ImmutableList, val appCurrencyCode: String, val isFeeLoading: Boolean, + val subtractedFee: BigDecimal?, ) : SendStates() /** Recipient state */ diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/SendAmountStateConverter.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/SendAmountStateConverter.kt index d12d7d3c28..f06a20bf48 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/SendAmountStateConverter.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/SendAmountStateConverter.kt @@ -41,6 +41,7 @@ internal class SendAmountStateConverter( notifications = persistentListOf(), isFeeLoading = false, appCurrencyCode = appCurrency.code, + subtractedFee = null, segmentedButtonConfig = if (status.value.fiatRate.isNullOrZero()) { persistentListOf() } else { diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/SendAmountSubtractConverter.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/SendAmountSubtractConverter.kt index bcad6f7468..bf0f52dd73 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/SendAmountSubtractConverter.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/SendAmountSubtractConverter.kt @@ -15,14 +15,16 @@ internal class SendAmountSubtractConverter( val state = currentStateProvider() val cryptoCurrencyStatus = cryptoCurrencyStatusProvider() val amountState = state.amountState ?: return state - val feeValue = state.feeState?.fee?.amount?.value ?: return state + val feeState = state.feeState ?: return state + val feeValue = feeState.fee?.amount?.value ?: return state val amountTextField = amountState.amountTextField val amountValue = amountTextField.cryptoAmount.value ?: return state val fiatRate = cryptoCurrencyStatus.value.fiatRate val cryptoDecimals = amountTextField.cryptoAmount.decimals val fiatDecimals = amountTextField.fiatAmount.decimals - val decimalCryptoValue = amountValue.minus(feeValue) + val feeDiff = amountState.subtractedFee?.let { feeValue.minus(it) } ?: feeValue + val decimalCryptoValue = amountValue.minus(feeDiff) if (decimalCryptoValue < BigDecimal.ZERO) return state @@ -33,6 +35,7 @@ internal class SendAmountSubtractConverter( return state.copy( sendState = state.sendState?.copy(isSubtract = true), amountState = amountState.copy( + subtractedFee = feeValue, amountTextField = amountTextField.copy( value = cryptoValue, fiatValue = fiatValue, diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeCalculation.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeCalculation.kt index 06a47de259..0d8e232875 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeCalculation.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeCalculation.kt @@ -13,7 +13,7 @@ internal fun checkFeeCoverage(state: SendUiState, cryptoCurrencyStatus: CryptoCu val balance = cryptoCurrencyStatus.value.amount ?: return false val fee = state.feeState?.fee?.amount?.value ?: return false val amount = state.amountState?.amountTextField?.cryptoAmount?.value ?: return false - return balance <= amount + fee + return balance < amount + fee } /** diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/custom/BitcoinCustomFeeConverter.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/custom/BitcoinCustomFeeConverter.kt index e42f25a9d5..8226a67504 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/custom/BitcoinCustomFeeConverter.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/custom/BitcoinCustomFeeConverter.kt @@ -59,8 +59,8 @@ internal class BitcoinCustomFeeConverter( ).toString(), decimals = SATOSHI_DECIMALS, symbol = "", - title = resourceReference(R.string.send_gas_price), - footer = resourceReference(R.string.send_gas_price_footer), + title = resourceReference(R.string.send_satoshi_per_byte_title), + footer = resourceReference(R.string.send_satoshi_per_byte_text), onValueChange = { clickIntents.onCustomFeeValueChange(FEE_SATOSHI_INDEX, it) }, keyboardOptions = KeyboardOptions( imeAction = if (checkExceedBalance(feeValue)) ImeAction.None else ImeAction.Done, diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fields/SendAmountFieldChangeConverter.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fields/SendAmountFieldChangeConverter.kt index a1fc8fbe25..8d7daa1665 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fields/SendAmountFieldChangeConverter.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fields/SendAmountFieldChangeConverter.kt @@ -38,6 +38,7 @@ internal class SendAmountFieldChangeConverter( return state.copy( amountState = amountState.copy( isPrimaryButtonEnabled = !isExceedBalance && !isZero, + subtractedFee = null, amountTextField = amountTextField.copy( value = cryptoValue, fiatValue = fiatValue, diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fields/SendAmountFieldMaxAmountConverter.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fields/SendAmountFieldMaxAmountConverter.kt index b29459e757..715d3ad4f8 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fields/SendAmountFieldMaxAmountConverter.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fields/SendAmountFieldMaxAmountConverter.kt @@ -34,6 +34,7 @@ internal class SendAmountFieldMaxAmountConverter( return state.copy( amountState = amountState.copy( isPrimaryButtonEnabled = true, + subtractedFee = null, amountTextField = amountTextField.copy( value = cryptoValue, fiatValue = fiatValue, diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/previewdata/AmountStatePreviewData.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/previewdata/AmountStatePreviewData.kt index 75e5ce97f4..91dbbde9bf 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/previewdata/AmountStatePreviewData.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/previewdata/AmountStatePreviewData.kt @@ -25,6 +25,7 @@ internal object AmountStatePreviewData { notifications = persistentListOf(), isFeeLoading = false, appCurrencyCode = "usd", + subtractedFee = null, amountTextField = SendTextField.AmountField( value = "123.123123123123123123", onValueChange = {}, From 83e6ed617144ef63d0d71c7e986f6ac37993b368 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 11 Apr 2024 19:03:10 +0100 Subject: [PATCH 38/67] Updated on 2026-08-14 --- .../analytics/paramsInterceptor/CardContextInterceptor.kt | 4 +--- .../tangem/tap/features/welcome/redux/WelcomeMiddleware.kt | 7 ++----- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/common/analytics/paramsInterceptor/CardContextInterceptor.kt b/app/src/main/java/com/tangem/tap/common/analytics/paramsInterceptor/CardContextInterceptor.kt index 6a963e544f..c881cd0127 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/paramsInterceptor/CardContextInterceptor.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/paramsInterceptor/CardContextInterceptor.kt @@ -15,7 +15,7 @@ import com.tangem.tap.features.demo.DemoHelper [REDACTED_AUTHOR] */ class CardContextInterceptor( - private val scanResponse: ScanResponse?, + private val scanResponse: ScanResponse, ) : ParamsInterceptor { override fun id(): String = CardContextInterceptor.id() @@ -28,8 +28,6 @@ class CardContextInterceptor( } override fun intercept(params: MutableMap) { - scanResponse ?: return - val card = scanResponse.card params[AnalyticsParam.BATCH] = card.batchId params[AnalyticsParam.PRODUCT_TYPE] = getProductType(scanResponse) diff --git a/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeMiddleware.kt b/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeMiddleware.kt index e2ff46f387..b4009829f8 100644 --- a/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeMiddleware.kt @@ -18,10 +18,7 @@ import com.tangem.domain.wallets.legacy.UserWalletsListManager.Lockable.UnlockTy import com.tangem.domain.wallets.legacy.unlockIfLockable import com.tangem.tap.* import com.tangem.tap.common.analytics.converters.ParamCardCurrencyConverter -import com.tangem.tap.common.extensions.dispatchOnMain -import com.tangem.tap.common.extensions.dispatchWithMain -import com.tangem.tap.common.extensions.inject -import com.tangem.tap.common.extensions.onUserWalletSelected +import com.tangem.tap.common.extensions.* import com.tangem.tap.common.redux.AppState import com.tangem.tap.features.intentHandler.handlers.BackgroundScanIntentHandler import com.tangem.tap.features.intentHandler.handlers.WalletConnectLinkIntentHandler @@ -141,7 +138,7 @@ internal class WelcomeMiddleware { val currency = ParamCardCurrencyConverter().convert( value = scanResponse.cardTypesResolver, ) - + Analytics.addContext(scanResponse) if (currency != null) { Analytics.send( event = Basic.SignedIn( From 00c8254540320c88410a94edecec4a379d0084fa Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 12 Apr 2024 17:17:39 +0500 Subject: [PATCH 39/67] Updated on 2026-08-14 --- .../presentation/state/confirm/SendNotificationFactory.kt | 7 ++++--- .../com/tangem/feature/swap/domain/SwapInteractorImpl.kt | 4 ++-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/confirm/SendNotificationFactory.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/confirm/SendNotificationFactory.kt index 6a4e8a383e..fb65e2a8b5 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/confirm/SendNotificationFactory.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/confirm/SendNotificationFactory.kt @@ -8,6 +8,7 @@ import com.tangem.core.ui.extensions.networkIconResId import com.tangem.core.ui.utils.BigDecimalFormatter import com.tangem.core.ui.utils.parseToBigDecimal import com.tangem.domain.common.extensions.fromNetworkId +import com.tangem.domain.common.extensions.minimalAmount import com.tangem.domain.tokens.GetBalanceNotEnoughForFeeWarningUseCase import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.tokens.model.CryptoCurrencyStatus @@ -225,13 +226,14 @@ internal class SendNotificationFactory( val cryptoCurrencyStatus = cryptoCurrencyStatusProvider() val balance = cryptoCurrencyStatus.value.amount ?: BigDecimal.ZERO val isTezos = cryptoCurrencyStatus.currency.network.id.value == Blockchain.Tezos.id + val threshold = Blockchain.Tezos.minimalAmount() val isTotalBalance = feeAmount.plus(sendAmount) >= balance if (!ignoreAmountReduce && isTotalBalance && isTezos) { add( SendNotification.Warning.HighFeeError( - amount = TEZOS_FEE_THRESHOLD.toPlainString(), + amount = threshold.toPlainString(), onConfirmClick = { - val reduceTo = sendAmount.minus(TEZOS_FEE_THRESHOLD).toPlainString() + val reduceTo = sendAmount.minus(threshold).toPlainString() clickIntents.onAmountReduceClick(reduceTo, SendNotification.Warning.HighFeeError::class.java) }, onCloseClick = { @@ -372,7 +374,6 @@ internal class SendNotificationFactory( companion object { private const val DOGECOIN_MINIMUM = "0.01" - private val TEZOS_FEE_THRESHOLD = BigDecimal("0.01") internal val FEE_MAX_DIFF = BigInteger("5") } } \ No newline at end of file diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt index 966b619bf8..0b77da443d 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt @@ -10,6 +10,7 @@ import com.tangem.blockchain.common.transaction.TransactionFee import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase import com.tangem.domain.appcurrency.extenstions.unwrap import com.tangem.domain.appcurrency.repository.AppCurrencyRepository +import com.tangem.domain.common.extensions.minimalAmount import com.tangem.domain.tokens.GetCryptoCurrencyStatusesSyncUseCase import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.tokens.model.CryptoCurrencyStatus @@ -433,7 +434,7 @@ internal class SwapInteractorImpl @Inject constructor( ) { val isTezos = fromTokenStatus.currency.network.id.value == Blockchain.Tezos.id if (isTezos && amount.value == fromTokenStatus.value.amount) { - warnings.add(Warning.ReduceAmountWarning(TEZOS_FEE_THRESHOLD)) + warnings.add(Warning.ReduceAmountWarning(Blockchain.Tezos.minimalAmount())) } } @@ -1595,6 +1596,5 @@ internal class SwapInteractorImpl @Inject constructor( private const val INCREASE_GAS_LIMIT_BY = 112 // 12% private const val INCREASE_GAS_LIMIT_FOR_SEND = 105 // 5% private const val INFINITY_SYMBOL = "∞" - private val TEZOS_FEE_THRESHOLD = BigDecimal("0.01") } } \ No newline at end of file From 80e007c6b6c1333a2a9ffbc6cad6dcbaab38e8b8 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 12 Apr 2024 17:18:44 +0500 Subject: [PATCH 40/67] Updated on 2026-08-14 --- .../features/send/impl/presentation/viewmodel/SendViewModel.kt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendViewModel.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendViewModel.kt index 8564920606..82ab855b4d 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendViewModel.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendViewModel.kt @@ -647,8 +647,7 @@ internal class SendViewModel @Inject constructor( private fun autoNextFromRecipient(type: EnterAddressSource?, isValidAddress: Boolean) { val isRecent = type == EnterAddressSource.RecentAddress - val isAddressOnly = uiState.recipientState?.memoTextField == null - if (isRecent && isAddressOnly && isValidAddress) onNextClick() + if (isRecent && isValidAddress) onNextClick() } // endregion From 16c1a70a4b3a511a1be5a3c9d9cc51ed58d2150a Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 12 Apr 2024 17:19:58 +0500 Subject: [PATCH 41/67] Updated on 2026-08-14 --- .../send/impl/presentation/state/SendStateFactory.kt | 6 +++--- .../features/send/impl/presentation/state/SendUiState.kt | 2 +- .../tangem/features/send/impl/presentation/ui/SendScreen.kt | 2 +- .../features/send/impl/presentation/ui/send/SendContent.kt | 6 +----- 4 files changed, 6 insertions(+), 10 deletions(-) diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendStateFactory.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendStateFactory.kt index 7a5c113219..24be0a0973 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendStateFactory.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendStateFactory.kt @@ -92,7 +92,7 @@ internal class SendStateFactory( event = consumedEvent(), isEditingDisabled = false, isBalanceHidden = false, - cryptoCurrencySymbol = "", + cryptoCurrencyName = "", ) fun getReadyState(): SendUiState { @@ -103,7 +103,7 @@ internal class SendStateFactory( ?: recipientStateConverter.convert(SendRecipientStateConverter.Data("", null)), feeState = state.feeState ?: feeStateConverter.convert(Unit), sendState = confirmStateConverter.convert(Unit), - cryptoCurrencySymbol = cryptoCurrencyStatusProvider().currency.symbol, + cryptoCurrencyName = cryptoCurrencyStatusProvider().currency.name, ) } @@ -115,7 +115,7 @@ internal class SendStateFactory( ?: recipientStateConverter.convert(SendRecipientStateConverter.Data(destinationAddress, memo)), feeState = state.feeState ?: feeStateConverter.convert(Unit), isEditingDisabled = true, - cryptoCurrencySymbol = cryptoCurrencyStatusProvider().currency.symbol, + cryptoCurrencyName = cryptoCurrencyStatusProvider().currency.name, ) } diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendUiState.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendUiState.kt index e49450d38b..586c55c978 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendUiState.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendUiState.kt @@ -23,7 +23,7 @@ import java.math.BigDecimal internal data class SendUiState( val clickIntents: SendClickIntents, val isEditingDisabled: Boolean, - val cryptoCurrencySymbol: String, + val cryptoCurrencyName: String, val amountState: SendStates.AmountState? = null, val recipientState: SendStates.RecipientState? = null, val feeState: SendStates.FeeState? = null, diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/SendScreen.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/SendScreen.kt index 2dcff9830c..0294fefa3b 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/SendScreen.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/SendScreen.kt @@ -43,7 +43,7 @@ internal fun SendScreen(uiState: SendUiState, currentState: SendUiCurrentScreen) SendUiStateType.Recipient -> resourceReference(R.string.send_recipient_label) SendUiStateType.Fee -> resourceReference(R.string.common_fee_selector_title) SendUiStateType.Send -> if (!sendState.isSuccess) { - resourceReference(R.string.send_summary_title, wrappedList(uiState.cryptoCurrencySymbol)) + resourceReference(R.string.send_summary_title, wrappedList(uiState.cryptoCurrencyName)) } else { null } diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/send/SendContent.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/send/SendContent.kt index 0c92c24fd5..78809756c4 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/send/SendContent.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/send/SendContent.kt @@ -35,11 +35,7 @@ internal fun SendContent(uiState: SendUiState) { LazyColumn( modifier = Modifier .fillMaxSize() - .padding( - start = TangemTheme.dimens.spacing16, - end = TangemTheme.dimens.spacing16, - bottom = TangemTheme.dimens.spacing16, - ), + .padding(horizontal = TangemTheme.dimens.spacing16), ) { blocks(uiState) tapHelp(isDisplay = sendState.showTapHelp) From 4538e9f03be30a21befe8ba05bebef880264ea22 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 12 Apr 2024 15:14:39 +0100 Subject: [PATCH 42/67] Updated on 2026-08-14 --- .../java/com/tangem/datasource/config/ConfigManagerImpl.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/core/datasource/src/main/java/com/tangem/datasource/config/ConfigManagerImpl.kt b/core/datasource/src/main/java/com/tangem/datasource/config/ConfigManagerImpl.kt index d26cc5e36e..d639cfbe6a 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/config/ConfigManagerImpl.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/config/ConfigManagerImpl.kt @@ -146,9 +146,9 @@ internal class ConfigManagerImpl @Inject constructor() : ConfigManager { blockBookRest = accessTokens.bitcoin?.blockBookRest, ), algorand = GetBlockAccessToken(rest = accessTokens.algorand?.rest), - zkSync = GetBlockAccessToken(rest = accessTokens.zksync?.rest), - polygonZkevm = GetBlockAccessToken(rest = accessTokens.polygonZkevm?.rest), - base = GetBlockAccessToken(rest = accessTokens.base?.rest), + zkSync = GetBlockAccessToken(rest = accessTokens.zksync?.jsonRPC), + polygonZkevm = GetBlockAccessToken(rest = accessTokens.polygonZkevm?.jsonRPC), + base = GetBlockAccessToken(rest = accessTokens.base?.jsonRPC), ) } } From fad7c0043dad63eb774b36e163e7c962183564a4 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 12 Apr 2024 19:48:36 +0500 Subject: [PATCH 43/67] Updated on 2026-08-14 --- core/res/src/main/res/values/strings.xml | 2 +- .../features/send/impl/presentation/state/fee/FeeCalculation.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml index a92316eb37..1251772f83 100644 --- a/core/res/src/main/res/values/strings.xml +++ b/core/res/src/main/res/values/strings.xml @@ -482,7 +482,7 @@ Send to A Memo/Destination Tag is a unique ID for differentiating transactions sent to the same recipient on the same network. Caution: Omitting a memo may lead to misplaced funds My wallets - Это способ измерения комиссии за отправку биткоин-транзакции. Он указывает на количество самой маленькой единицы биткоина (сатоши) за каждый байт данных в транзакции. Чем выше это число, тем быстрее будет обработана транзакция сетью. + The fee for a Bitcoin transaction is measured by the number of the smallest Bitcoin unit (Satoshi) per byte of data. The higher this number, the faster the transaction will be processed. Satoshi per vbyte Sending... Tap any field to change it diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeCalculation.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeCalculation.kt index 0d8e232875..57e567a586 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeCalculation.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeCalculation.kt @@ -13,7 +13,7 @@ internal fun checkFeeCoverage(state: SendUiState, cryptoCurrencyStatus: CryptoCu val balance = cryptoCurrencyStatus.value.amount ?: return false val fee = state.feeState?.fee?.amount?.value ?: return false val amount = state.amountState?.amountTextField?.cryptoAmount?.value ?: return false - return balance < amount + fee + return balance < amount + fee && balance > fee } /** From 9a6abc262f314341eadffe947bf04607be9c1b78 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 12 Apr 2024 17:06:16 +0100 Subject: [PATCH 44/67] Updated on 2026-08-14 --- .../tap/features/onboarding/OnboardingHelper.kt | 3 ++- .../main/java/com/tangem/domain/demo/DemoConfig.kt | 12 ++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt b/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt index 6d69e20e28..9348207ccf 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt @@ -16,6 +16,7 @@ import com.tangem.domain.userwallets.UserWalletIdBuilder import com.tangem.tap.* import com.tangem.tap.common.analytics.converters.ParamCardCurrencyConverter import com.tangem.tap.common.extensions.* +import com.tangem.tap.features.demo.DemoHelper import com.tangem.tap.features.saveWallet.redux.SaveWalletAction import com.tangem.tap.proxy.redux.DaggerGraphState import kotlinx.coroutines.delay @@ -41,7 +42,7 @@ object OnboardingHelper { response.cardTypesResolver.isWallet2() || response.cardTypesResolver.isShibaWallet() -> { val emptyWallets = response.card.wallets.isEmpty() val activationInProgress = onboardingManager?.isActivationInProgress(cardId) - val backupNotActive = response.card.backupStatus?.isActive != true + val backupNotActive = response.card.backupStatus?.isActive != true && !DemoHelper.isDemoCard(response) emptyWallets || activationInProgress == true || backupNotActive } diff --git a/domain/demo/src/main/java/com/tangem/domain/demo/DemoConfig.kt b/domain/demo/src/main/java/com/tangem/domain/demo/DemoConfig.kt index 304c70af48..b17e293800 100644 --- a/domain/demo/src/main/java/com/tangem/domain/demo/DemoConfig.kt +++ b/domain/demo/src/main/java/com/tangem/domain/demo/DemoConfig.kt @@ -405,6 +405,18 @@ class DemoConfig { "AF04000000012014", "AF04000000012022", "AF04000000012030", + "AF15000000257889", + "AF15000000257897", + "AF15000000637809", + "AF15000000640282", + "AF15000001187424", + "AF15000001187408", + "AF15000001187416", + "AF15000001195781", + "AF15000001195773", + "AF15000001195799", + "AF19000000000038", + "AC19000000000064", ) @Suppress("ClassOrdering") From 7d15269083699a7d55aef1b4d3e9c8d28e10b0d2 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 12 Apr 2024 17:20:20 +0500 Subject: [PATCH 45/67] Updated on 2026-08-14 --- .../repository/DefaultTxHistoryRepository.kt | 15 +++++++++++++++ .../txhistory/repository/TxHistoryRepository.kt | 3 +++ .../usecase/GetExplorerTransactionUrlUseCase.kt | 15 +++++++++++++++ .../impl/presentation/state/SendStateFactory.kt | 9 +-------- .../impl/presentation/viewmodel/SendViewModel.kt | 13 ++++++++++--- 5 files changed, 44 insertions(+), 11 deletions(-) diff --git a/data/txhistory/src/main/kotlin/com/tangem/data/txhistory/repository/DefaultTxHistoryRepository.kt b/data/txhistory/src/main/kotlin/com/tangem/data/txhistory/repository/DefaultTxHistoryRepository.kt index dba0ed594a..64268e3c0c 100644 --- a/data/txhistory/src/main/kotlin/com/tangem/data/txhistory/repository/DefaultTxHistoryRepository.kt +++ b/data/txhistory/src/main/kotlin/com/tangem/data/txhistory/repository/DefaultTxHistoryRepository.kt @@ -9,6 +9,7 @@ import com.tangem.data.common.cache.CacheRegistry import com.tangem.data.txhistory.repository.paging.TxHistoryPagingSource import com.tangem.datasource.local.txhistory.TxHistoryItemsStore import com.tangem.datasource.local.userwallet.UserWalletsStore +import com.tangem.domain.common.extensions.fromNetworkId import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.tokens.model.Network import com.tangem.domain.txhistory.models.Page @@ -69,6 +70,7 @@ class DefaultTxHistoryRepository( return pager.flow } + @Deprecated("Replace with getTxExploreUrl [UserWalletId, Network] instead") override fun getTxExploreUrl(txHash: String, networkId: Network.ID): String { val blockchain = Blockchain.fromId(networkId.value) return when (val txExploreState = blockchain.getExploreTxUrl(txHash)) { @@ -77,6 +79,19 @@ class DefaultTxHistoryRepository( } } + override suspend fun getTxExploreUrl(userWalletId: UserWalletId, network: Network): String { + val blockchain = Blockchain.fromNetworkId(network.id.value) + val walletManager = walletManagersFacade.getOrCreateWalletManager( + userWalletId = userWalletId, + network = network, + ) + val lastTxHash = walletManager?.wallet?.recentTransactions?.last()?.hash.orEmpty() + return when (val txExploreState = blockchain?.getExploreTxUrl(lastTxHash)) { + is TxExploreState.Url -> txExploreState.url + else -> "" + } + } + override suspend fun getFixedSizeTxHistoryItems( userWalletId: UserWalletId, currency: CryptoCurrency, diff --git a/domain/txhistory/src/main/kotlin/com/tangem/domain/txhistory/repository/TxHistoryRepository.kt b/domain/txhistory/src/main/kotlin/com/tangem/domain/txhistory/repository/TxHistoryRepository.kt index b010a54db4..652b15c85b 100644 --- a/domain/txhistory/src/main/kotlin/com/tangem/domain/txhistory/repository/TxHistoryRepository.kt +++ b/domain/txhistory/src/main/kotlin/com/tangem/domain/txhistory/repository/TxHistoryRepository.kt @@ -24,6 +24,9 @@ interface TxHistoryRepository { fun getTxExploreUrl(txHash: String, networkId: Network.ID): String + /** Get transaction url in explorer via last transaction from wallet's recentTransactions list */ + suspend fun getTxExploreUrl(userWalletId: UserWalletId, network: Network): String + @Throws(TxHistoryListError::class) suspend fun getFixedSizeTxHistoryItems( userWalletId: UserWalletId, diff --git a/domain/txhistory/src/main/kotlin/com/tangem/domain/txhistory/usecase/GetExplorerTransactionUrlUseCase.kt b/domain/txhistory/src/main/kotlin/com/tangem/domain/txhistory/usecase/GetExplorerTransactionUrlUseCase.kt index 974d439c7e..cedb309901 100644 --- a/domain/txhistory/src/main/kotlin/com/tangem/domain/txhistory/usecase/GetExplorerTransactionUrlUseCase.kt +++ b/domain/txhistory/src/main/kotlin/com/tangem/domain/txhistory/usecase/GetExplorerTransactionUrlUseCase.kt @@ -6,10 +6,12 @@ import arrow.core.raise.either import com.tangem.domain.tokens.model.Network import com.tangem.domain.txhistory.models.TxStatusError import com.tangem.domain.txhistory.repository.TxHistoryRepository +import com.tangem.domain.wallets.models.UserWalletId class GetExplorerTransactionUrlUseCase( private val repository: TxHistoryRepository, ) { + @Deprecated("Replace with invoke [UserWalletId, Network]") operator fun invoke(txHash: String, networkId: Network.ID): Either { return either { catch( @@ -22,4 +24,17 @@ class GetExplorerTransactionUrlUseCase( ) } } + + suspend operator fun invoke(userWalletId: UserWalletId, network: Network): Either { + return either { + catch( + block = { + repository.getTxExploreUrl(userWalletId, network).ifEmpty { + raise(TxStatusError.EmptyUrlError) + } + }, + catch = { raise(TxStatusError.DataError(it)) }, + ) + } + } } \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendStateFactory.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendStateFactory.kt index 24be0a0973..6861d18b0f 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendStateFactory.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendStateFactory.kt @@ -8,7 +8,6 @@ import com.tangem.core.ui.extensions.resourceReference import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.domain.txhistory.models.TxHistoryItem -import com.tangem.domain.txhistory.usecase.GetExplorerTransactionUrlUseCase import com.tangem.domain.wallets.models.UserWallet import com.tangem.domain.wallets.usecase.ValidateWalletMemoUseCase import com.tangem.features.send.impl.R @@ -36,7 +35,6 @@ internal class SendStateFactory( private val feeCryptoCurrencyStatusProvider: Provider, private val isTapHelpPreviewEnabledProvider: Provider, private val validateWalletMemoUseCase: ValidateWalletMemoUseCase, - private val getExplorerTransactionUrlUseCase: GetExplorerTransactionUrlUseCase, ) { private val iconStateConverter by lazy(::CryptoCurrencyToIconStateConverter) @@ -251,14 +249,9 @@ internal class SendStateFactory( ) } - fun getTransactionSendState(txData: TransactionData): SendUiState { + fun getTransactionSendState(txData: TransactionData, txUrl: String): SendUiState { val state = currentStateProvider() - val cryptoCurrency = cryptoCurrencyStatusProvider().currency val sendState = state.sendState ?: return state - val txUrl = getExplorerTransactionUrlUseCase( - txHash = txData.hash.orEmpty(), - networkId = cryptoCurrency.network.id, - ).getOrElse { "" } return state.copy( sendState = sendState.copy( transactionDate = txData.date?.timeInMillis ?: System.currentTimeMillis(), diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendViewModel.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendViewModel.kt index 82ab855b4d..9f1089fccf 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendViewModel.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendViewModel.kt @@ -89,9 +89,9 @@ internal class SendViewModel @Inject constructor( private val parseQrCodeUseCase: ParseQrCodeUseCase, private val isSendTapHelpEnabledUseCase: IsSendTapHelpEnabledUseCase, private val neverShowTapHelpUseCase: NeverShowTapHelpUseCase, + private val getExplorerTransactionUrlUseCase: GetExplorerTransactionUrlUseCase, currencyChecksRepository: CurrencyChecksRepository, isFeeApproximateUseCase: IsFeeApproximateUseCase, - getExplorerTransactionUrlUseCase: GetExplorerTransactionUrlUseCase, validateWalletMemoUseCase: ValidateWalletMemoUseCase, getBalanceNotEnoughForFeeWarningUseCase: GetBalanceNotEnoughForFeeWarningUseCase, savedStateHandle: SavedStateHandle, @@ -123,7 +123,6 @@ internal class SendViewModel @Inject constructor( cryptoCurrencyStatusProvider = Provider { cryptoCurrencyStatus }, feeCryptoCurrencyStatusProvider = Provider { feeCryptoCurrencyStatus }, validateWalletMemoUseCase = validateWalletMemoUseCase, - getExplorerTransactionUrlUseCase = getExplorerTransactionUrlUseCase, isTapHelpPreviewEnabledProvider = Provider { isTapHelpPreviewEnabled }, ) @@ -845,13 +844,21 @@ internal class SendViewModel @Inject constructor( }, ifRight = { uiState = stateFactory.getSendingStateUpdate(isSending = false) - uiState = stateFactory.getTransactionSendState(txData) + updateTransactionStatus(txData) scheduleBalanceUpdate() analyticsEventHandler.send(SendAnalyticEvents.TransactionScreenOpened) }, ) } + private suspend fun updateTransactionStatus(txData: TransactionData) { + val txUrl = getExplorerTransactionUrlUseCase( + userWalletId = userWalletId, + network = cryptoCurrency.network, + ).getOrElse { "" } + uiState = stateFactory.getTransactionSendState(txData, txUrl) + } + private fun scheduleBalanceUpdate() { viewModelScope.launch(dispatchers.io) { delay(BALANCE_UPDATE_DELAY) From c747f6f38446ae95a718a00b1747973fba648e4c Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 15 Apr 2024 13:49:08 +0100 Subject: [PATCH 46/67] Updated on 2026-08-14 --- .../src/main/assets/configs/feature_toggles_config.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json b/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json index c3c879c727..7a8e690d21 100644 --- a/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json +++ b/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json @@ -25,6 +25,6 @@ }, { "name": "WC_SOLANA_TX_SIGN_ENABLED", - "version": "5.9.0" + "version": "5.11.0" } ] From 088295ceaf3995de7e8af4762d5fbc66f6eee27f Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 15 Apr 2024 17:56:58 +0500 Subject: [PATCH 47/67] Updated on 2026-08-14 --- .../core/ui/components/fields/SimpleTextField.kt | 14 ++++++-------- .../state/confirm/SendNotificationFactory.kt | 12 +----------- 2 files changed, 7 insertions(+), 19 deletions(-) diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/fields/SimpleTextField.kt b/core/ui/src/main/java/com/tangem/core/ui/components/fields/SimpleTextField.kt index ed91b52667..b9cba56550 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/fields/SimpleTextField.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/fields/SimpleTextField.kt @@ -72,14 +72,12 @@ fun SimpleTextField( } var lastTextValue by remember(value) { - derivedStateOf { - val isSelectionLastIndex = textFieldValueState.selection.end == textFieldValueState.text.lastIndex - if (textFieldValueState.text.isBlank() || isSelectionLastIndex) { - textFieldValueState = textFieldValueState.copy( - text = value, - selection = getValueRange(value), - ) - } + val isSelectionLastIndex = textFieldValueState.selection.end == textFieldValueState.text.lastIndex + if (textFieldValueState.text.isBlank() || isSelectionLastIndex) { + textFieldValueState = textFieldValueState.copy( + text = value, + selection = getValueRange(value), + ) } mutableStateOf(value) } diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/confirm/SendNotificationFactory.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/confirm/SendNotificationFactory.kt index fb65e2a8b5..68d334e0ae 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/confirm/SendNotificationFactory.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/confirm/SendNotificationFactory.kt @@ -59,7 +59,6 @@ internal class SendNotificationFactory( // errors addExceedBalanceNotification(feeAmount, amountValue) addExceedsBalanceNotification(feeState.fee) - addInvalidAmountNotification(sendState.isSubtract, amountValue) addMinimumAmountErrorNotification(feeAmount, amountValue) addDustWarningNotification(feeAmount, amountValue) addTransactionLimitErrorNotification(feeAmount, amountValue) @@ -106,15 +105,6 @@ internal class SendNotificationFactory( } } - private fun MutableList.addInvalidAmountNotification( - isSubtractAmount: Boolean, - receivedAmount: BigDecimal, - ) { - if (isSubtractAmount && receivedAmount <= BigDecimal.ZERO) { - add(SendNotification.Error.InvalidAmount) - } - } - private fun MutableList.addMinimumAmountErrorNotification( feeAmount: BigDecimal, receivedAmount: BigDecimal, @@ -227,7 +217,7 @@ internal class SendNotificationFactory( val balance = cryptoCurrencyStatus.value.amount ?: BigDecimal.ZERO val isTezos = cryptoCurrencyStatus.currency.network.id.value == Blockchain.Tezos.id val threshold = Blockchain.Tezos.minimalAmount() - val isTotalBalance = feeAmount.plus(sendAmount) >= balance + val isTotalBalance = feeAmount.plus(sendAmount) >= balance && balance > threshold if (!ignoreAmountReduce && isTotalBalance && isTezos) { add( SendNotification.Warning.HighFeeError( From 65f956fddf9f35442f2a1cc2f32068c2da3a5c4c Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 15 Apr 2024 19:16:12 +0500 Subject: [PATCH 48/67] Updated on 2026-08-14 --- .../presentation/viewmodel/SendViewModel.kt | 28 ++++++++++++------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendViewModel.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendViewModel.kt index 9f1089fccf..c92d4b895d 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendViewModel.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendViewModel.kt @@ -1,5 +1,6 @@ package com.tangem.features.send.impl.presentation.viewmodel +import android.os.SystemClock import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue @@ -191,6 +192,7 @@ internal class SendViewModel @Inject constructor( private var qrScannerJobHolder = JobHolder() private var sendIdleTimer = 0L + private var feeIdleTimer = 0L init { subscribeOnCurrencyStatusUpdates() @@ -444,7 +446,7 @@ internal class SendViewModel @Inject constructor( .onEach { when (it.type) { SendUiStateType.Fee -> loadFee() - SendUiStateType.Send -> sendIdleTimer = System.currentTimeMillis() + SendUiStateType.Send -> sendIdleTimer = SystemClock.elapsedRealtime() else -> Unit } } @@ -683,6 +685,9 @@ internal class SendViewModel @Inject constructor( } private fun loadFee(isToNextState: Boolean = false) { + // debouncing fee request + if (SystemClock.elapsedRealtime() - feeIdleTimer < FEE_UPDATE_DELAY) return + viewModelScope.launch(dispatchers.main) { val isShowStatus = uiState.feeState?.fee == null if (isShowStatus) { @@ -690,6 +695,7 @@ internal class SendViewModel @Inject constructor( } val result = callFeeUseCase()?.fold( ifRight = { + feeIdleTimer = SystemClock.elapsedRealtime() uiState = feeStateFactory.onFeeOnLoadedState(it) if (isToNextState && !onFeeCoverageAlert()) { stateRouter.showSend() @@ -747,12 +753,13 @@ internal class SendViewModel @Inject constructor( if (sendState.isSuccess) popBackStack() uiState = stateFactory.getSendingStateUpdate(isSending = true) - if (System.currentTimeMillis() - sendIdleTimer < CHECK_FEE_UPDATE_DELAY) { + if (SystemClock.elapsedRealtime() - sendIdleTimer < CHECK_FEE_UPDATE_DELAY) { verifyAndSendTransaction() } else { onCheckFeeUpdate() + feeIdleTimer = SystemClock.elapsedRealtime() + sendIdleTimer = SystemClock.elapsedRealtime() } - sendIdleTimer = System.currentTimeMillis() } override fun showAmount() { @@ -917,13 +924,14 @@ internal class SendViewModel @Inject constructor( } // endregion - companion object { - private const val CHECK_FEE_UPDATE_DELAY = 60_000L - private const val BALANCE_UPDATE_DELAY = 10_000L + private companion object { + const val CHECK_FEE_UPDATE_DELAY = 60_000L + const val FEE_UPDATE_DELAY = 10_000L + const val BALANCE_UPDATE_DELAY = 10_000L - private const val RU_LOCALE = "ru" - private const val EN_LOCALE = "en" - private const val FEE_READ_MORE_URL_FIRST_PART = "https://tangem.com/" - private const val FEE_READ_MORE_URL_SECOND_PART = "/blog/post/what-is-a-transaction-fee-and-why-do-we-need-it/" + const val RU_LOCALE = "ru" + const val EN_LOCALE = "en" + const val FEE_READ_MORE_URL_FIRST_PART = "https://tangem.com/" + const val FEE_READ_MORE_URL_SECOND_PART = "/blog/post/what-is-a-transaction-fee-and-why-do-we-need-it/" } } \ No newline at end of file From 1d7133b40ed806b7a07bb96185eb09c2b2e45ac1 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 15 Apr 2024 15:44:01 +0100 Subject: [PATCH 49/67] Updated on 2026-08-14 --- domain/demo/src/main/java/com/tangem/domain/demo/DemoConfig.kt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/domain/demo/src/main/java/com/tangem/domain/demo/DemoConfig.kt b/domain/demo/src/main/java/com/tangem/domain/demo/DemoConfig.kt index b17e293800..7b9bb1a996 100644 --- a/domain/demo/src/main/java/com/tangem/domain/demo/DemoConfig.kt +++ b/domain/demo/src/main/java/com/tangem/domain/demo/DemoConfig.kt @@ -399,6 +399,9 @@ class DemoConfig { "AB02000000048063", "AB02000000023736", "AB02000000058187", + "AB02000000000007", + "AC03000000076229", + "AF04000000000118", // Wallet 2 "AF04000000012006", From ce82e49ef4a58365e2f352ca56e4efd877497e83 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 16 Apr 2024 12:15:22 +0500 Subject: [PATCH 50/67] Updated on 2026-08-14 --- core/res/src/main/res/values/strings.xml | 2 +- .../usecase/GetFixedTxHistoryItemsUseCase.kt | 11 +++ .../domain/SendRecipientListContent.kt | 5 +- .../presentation/state/SendStateFactory.kt | 49 ++++++++-- ...t => SendRecipientHistoryListConverter.kt} | 49 +++------- .../recipient/SendRecipientStateConverter.kt | 6 +- .../SendRecipientWalletListConverter.kt | 39 ++++++++ .../state/recipient/utils/RecentListUtils.kt | 32 ++++++ .../ui/recipient/ListItemWithIcon.kt | 97 ++++++++++++++++++- .../ui/recipient/SendRecipientContent.kt | 58 +++++------ .../presentation/viewmodel/SendViewModel.kt | 66 ++++++------- 11 files changed, 294 insertions(+), 120 deletions(-) rename features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/recipient/{SendRecipientListConverter.kt => SendRecipientHistoryListConverter.kt} (71%) create mode 100644 features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/recipient/SendRecipientWalletListConverter.kt create mode 100644 features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/recipient/utils/RecentListUtils.kt diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml index 1251772f83..9310a0153a 100644 --- a/core/res/src/main/res/values/strings.xml +++ b/core/res/src/main/res/values/strings.xml @@ -422,7 +422,7 @@ Already included in the entered address Subtract Not enough funds to cover the network fee. Do you want to subtract the amount required to cover the fee? - The commission amount is %@ times the recommended amount. Make sure that the custom settings are correct. + The commission amount is %s times the recommended amount. Make sure that the custom settings are correct. You specified a commission below the recommended amount, which could cause a delay in your transaction. Continue? Reason: %1$s\nCode: %2$s The transaction is not completed diff --git a/domain/txhistory/src/main/kotlin/com/tangem/domain/txhistory/usecase/GetFixedTxHistoryItemsUseCase.kt b/domain/txhistory/src/main/kotlin/com/tangem/domain/txhistory/usecase/GetFixedTxHistoryItemsUseCase.kt index aa93bec246..8b5a463618 100644 --- a/domain/txhistory/src/main/kotlin/com/tangem/domain/txhistory/usecase/GetFixedTxHistoryItemsUseCase.kt +++ b/domain/txhistory/src/main/kotlin/com/tangem/domain/txhistory/usecase/GetFixedTxHistoryItemsUseCase.kt @@ -33,4 +33,15 @@ class GetFixedTxHistoryItemsUseCase( } }.mapLeft { TxHistoryListError.DataError(it) } } + + suspend fun getSync( + userWalletId: UserWalletId, + currency: CryptoCurrency, + pageSize: Int = DEFAULT_PAGE_SIZE, + refresh: Boolean = false, + ): Either> { + return Either.catch { + repository.getFixedSizeTxHistoryItems(userWalletId, currency, pageSize, refresh) + }.mapLeft { TxHistoryListError.DataError(it) } + } } \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/domain/SendRecipientListContent.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/domain/SendRecipientListContent.kt index 0837f1f27c..03e9414c0a 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/domain/SendRecipientListContent.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/domain/SendRecipientListContent.kt @@ -5,10 +5,11 @@ import com.tangem.core.ui.extensions.TextReference data class SendRecipientListContent( val id: String, - val title: TextReference, - val subtitle: TextReference, + val title: TextReference = TextReference.EMPTY, + val subtitle: TextReference = TextReference.EMPTY, val timestamp: TextReference? = null, val subtitleEndOffset: Int = 0, @DrawableRes val subtitleIconRes: Int? = null, val isVisible: Boolean = true, + val isLoading: Boolean = false, ) \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendStateFactory.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendStateFactory.kt index 6861d18b0f..f3f304b7ff 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendStateFactory.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendStateFactory.kt @@ -4,6 +4,7 @@ import arrow.core.getOrElse import com.tangem.blockchain.common.TransactionData import com.tangem.core.ui.components.currency.tokenicon.converter.CryptoCurrencyToIconStateConverter import com.tangem.core.ui.event.consumedEvent +import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.extensions.resourceReference import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.tokens.model.CryptoCurrencyStatus @@ -17,12 +18,14 @@ import com.tangem.features.send.impl.presentation.state.amount.SendAmountSubtrac import com.tangem.features.send.impl.presentation.state.confirm.SendConfirmStateConverter import com.tangem.features.send.impl.presentation.state.fee.SendFeeStateConverter import com.tangem.features.send.impl.presentation.state.fields.SendAmountFieldConverter -import com.tangem.features.send.impl.presentation.state.recipient.SendRecipientListConverter +import com.tangem.features.send.impl.presentation.state.recipient.SendRecipientHistoryListConverter import com.tangem.features.send.impl.presentation.state.recipient.SendRecipientStateConverter +import com.tangem.features.send.impl.presentation.state.recipient.SendRecipientWalletListConverter import com.tangem.features.send.impl.presentation.viewmodel.SendClickIntents import com.tangem.utils.Provider import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf +import kotlinx.collections.immutable.toPersistentList import timber.log.Timber @Suppress("LongParameterList") @@ -77,9 +80,11 @@ internal class SendStateFactory( isTapHelpPreviewEnabledProvider = isTapHelpPreviewEnabledProvider, ) } - private val recipientListStateConverter by lazy(LazyThreadSafetyMode.NONE) { - SendRecipientListConverter( - currentStateProvider = currentStateProvider, + private val recipientWalletListStateConverter by lazy(LazyThreadSafetyMode.NONE) { + SendRecipientWalletListConverter() + } + private val recipientHistoryListStateConverter by lazy(LazyThreadSafetyMode.NONE) { + SendRecipientHistoryListConverter( cryptoCurrencyStatusProvider = cryptoCurrencyStatusProvider, ) } @@ -123,11 +128,23 @@ internal class SendStateFactory( //endregion //region recipient - fun onLoadedRecipientList(wallets: List, txHistory: List): SendUiState = - recipientListStateConverter.convert( - wallets = wallets, - txHistory = txHistory, + fun onLoadedWalletsList(wallets: List): SendUiState { + val state = currentStateProvider() + return state.copy( + recipientState = state.recipientState?.copy( + wallets = recipientWalletListStateConverter.convert(wallets), + ), ) + } + + fun onLoadedHistoryList(txHistory: List): SendUiState { + val state = currentStateProvider() + return state.copy( + recipientState = state.recipientState?.copy( + recent = recipientHistoryListStateConverter.convert(txHistory), + ), + ) + } fun onRecipientAddressValueChange(value: String, isXAddress: Boolean = false): SendUiState { val state = currentStateProvider() @@ -228,6 +245,22 @@ internal class SendStateFactory( ), ) } + + fun getHiddenRecentListState(isAddressInWallet: Boolean, isValidAddress: Boolean): SendUiState { + val state = currentStateProvider() + val recipientState = state.recipientState ?: return state + val isNotValid = isAddressInWallet || !isValidAddress + return state.copy( + recipientState = recipientState.copy( + recent = recipientState.recent.map { recent -> + recent.copy(isVisible = isNotValid && (recent.isLoading || recent.title != TextReference.EMPTY)) + }.toPersistentList(), + wallets = recipientState.wallets.map { wallet -> + wallet.copy(isVisible = isNotValid && (wallet.isLoading || wallet.title != TextReference.EMPTY)) + }.toPersistentList(), + ), + ) + } //endregion //region send diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/recipient/SendRecipientListConverter.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/recipient/SendRecipientHistoryListConverter.kt similarity index 71% rename from features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/recipient/SendRecipientListConverter.kt rename to features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/recipient/SendRecipientHistoryListConverter.kt index 046c7a153d..f6ccb6ba98 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/recipient/SendRecipientListConverter.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/recipient/SendRecipientHistoryListConverter.kt @@ -10,49 +10,26 @@ import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.domain.txhistory.models.TxHistoryItem import com.tangem.features.send.impl.R -import com.tangem.features.send.impl.presentation.domain.AvailableWallet import com.tangem.features.send.impl.presentation.domain.SendRecipientListContent -import com.tangem.features.send.impl.presentation.state.SendUiState +import com.tangem.features.send.impl.presentation.state.recipient.utils.RECENT_DEFAULT_COUNT +import com.tangem.features.send.impl.presentation.state.recipient.utils.RECENT_KEY_TAG +import com.tangem.features.send.impl.presentation.state.recipient.utils.emptyListState import com.tangem.utils.Provider +import com.tangem.utils.converter.Converter import com.tangem.utils.toFormattedCurrencyString +import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.toPersistentList -internal class SendRecipientListConverter( - private val currentStateProvider: Provider, +internal class SendRecipientHistoryListConverter( private val cryptoCurrencyStatusProvider: Provider, -) { +) : Converter, ImmutableList> { - fun convert(wallets: List, txHistory: List): SendUiState { + override fun convert(value: List): ImmutableList { val cryptoCurrency = cryptoCurrencyStatusProvider().currency - val state = currentStateProvider() - val recipientState = state.recipientState ?: return state - - return state.copy( - recipientState = recipientState.copy( - wallets = wallets.filterWallets(), - recent = txHistory.filterRecipients(cryptoCurrency), - ), - ) - } - - private fun List.filterWallets() = this.filterNotNull() - .groupBy { item -> item.name } - .values.map { - it.mapIndexed { index, item -> - val name = if (it.size > 1) { - "${item.name} ${index.inc()}" - } else { - item.name - } - SendRecipientListContent( - id = item.address, - title = TextReference.Str(item.address), - subtitle = TextReference.Str(name), - ) - } + return value.filterRecipients(cryptoCurrency).ifEmpty { + emptyListState(RECENT_KEY_TAG, RECENT_DEFAULT_COUNT) } - .flatten() - .toPersistentList() + } private fun List.filterRecipients(cryptoCurrency: CryptoCurrency) = this.filter { item -> val isTransfer = item.type == TxHistoryItem.TransactionType.Transfer @@ -65,9 +42,9 @@ internal class SendRecipientListConverter( isTransfer && isSingleAddress && isNotContract } .take(RECENT_LIST_SIZE) - .map { tx -> + .mapIndexed { index, tx -> SendRecipientListContent( - id = tx.txHash, + id = "$RECENT_KEY_TAG$index", title = tx.extractAddress(), subtitle = stringReference(tx.getAmount(cryptoCurrency).trim()), timestamp = tx.extractTimestamp(), diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/recipient/SendRecipientStateConverter.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/recipient/SendRecipientStateConverter.kt index dd7db52c45..9b3f770fcf 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/recipient/SendRecipientStateConverter.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/recipient/SendRecipientStateConverter.kt @@ -2,10 +2,10 @@ package com.tangem.features.send.impl.presentation.state.recipient import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.features.send.impl.presentation.state.SendStates +import com.tangem.features.send.impl.presentation.state.recipient.utils.* import com.tangem.features.send.impl.presentation.viewmodel.SendClickIntents import com.tangem.utils.Provider import com.tangem.utils.converter.Converter -import kotlinx.collections.immutable.persistentListOf internal class SendRecipientStateConverter( private val clickIntents: SendClickIntents, @@ -26,8 +26,8 @@ internal class SendRecipientStateConverter( memoTextField = memoFieldConverter.convertOrNull(value.memo), network = cryptoCurrencyStatusProvider().currency.network.name, isPrimaryButtonEnabled = false, - wallets = persistentListOf(), - recent = persistentListOf(), + wallets = loadingListState(WALLET_KEY_TAG, WALLET_DEFAULT_COUNT), + recent = loadingListState(RECENT_KEY_TAG, RECENT_DEFAULT_COUNT), ) } diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/recipient/SendRecipientWalletListConverter.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/recipient/SendRecipientWalletListConverter.kt new file mode 100644 index 0000000000..855e5d3a3b --- /dev/null +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/recipient/SendRecipientWalletListConverter.kt @@ -0,0 +1,39 @@ +package com.tangem.features.send.impl.presentation.state.recipient + +import com.tangem.core.ui.extensions.TextReference +import com.tangem.features.send.impl.presentation.domain.AvailableWallet +import com.tangem.features.send.impl.presentation.domain.SendRecipientListContent +import com.tangem.features.send.impl.presentation.state.recipient.utils.WALLET_DEFAULT_COUNT +import com.tangem.features.send.impl.presentation.state.recipient.utils.WALLET_KEY_TAG +import com.tangem.features.send.impl.presentation.state.recipient.utils.emptyListState +import com.tangem.utils.converter.Converter +import kotlinx.collections.immutable.PersistentList +import kotlinx.collections.immutable.toPersistentList + +internal class SendRecipientWalletListConverter : + Converter, PersistentList> { + override fun convert(value: List): PersistentList { + return value.filterWallets().ifEmpty { + emptyListState(WALLET_KEY_TAG, WALLET_DEFAULT_COUNT) + } + } + + private fun List.filterWallets() = this.filterNotNull() + .groupBy { item -> item.name } + .values.map { + it.mapIndexed { index, item -> + val name = if (it.size > 1) { + "${item.name} ${index.inc()}" + } else { + item.name + } + SendRecipientListContent( + id = "${WALLET_KEY_TAG}$index", + title = TextReference.Str(item.address), + subtitle = TextReference.Str(name), + ) + } + } + .flatten() + .toPersistentList() +} \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/recipient/utils/RecentListUtils.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/recipient/utils/RecentListUtils.kt new file mode 100644 index 0000000000..c2d5ad9739 --- /dev/null +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/recipient/utils/RecentListUtils.kt @@ -0,0 +1,32 @@ +package com.tangem.features.send.impl.presentation.state.recipient.utils + +import com.tangem.features.send.impl.presentation.domain.SendRecipientListContent +import kotlinx.collections.immutable.toPersistentList + +internal const val WALLET_DEFAULT_COUNT = 1 +internal const val RECENT_DEFAULT_COUNT = 3 +internal const val WALLET_KEY_TAG = "wallet" +internal const val RECENT_KEY_TAG = "recent" + +internal fun loadingListState(tag: String, count: Int) = buildList { + repeat(count) { + add( + SendRecipientListContent( + id = "$tag$it", + isLoading = true, + ), + ) + } +}.toPersistentList() + +internal fun emptyListState(tag: String, count: Int) = buildList { + repeat(count) { + add( + SendRecipientListContent( + id = "$tag$it", + isLoading = false, + isVisible = false, + ), + ) + } +}.toPersistentList() \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/recipient/ListItemWithIcon.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/recipient/ListItemWithIcon.kt index c33885f290..9ef9f60a21 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/recipient/ListItemWithIcon.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/recipient/ListItemWithIcon.kt @@ -1,6 +1,10 @@ package com.tangem.features.send.impl.presentation.ui.recipient import androidx.annotation.DrawableRes +import androidx.compose.animation.AnimatedContent +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.animation.togetherWith import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.* @@ -9,6 +13,7 @@ import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.Icon import androidx.compose.runtime.Composable import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.res.painterResource @@ -16,6 +21,8 @@ import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider +import com.tangem.core.ui.components.CircleShimmer +import com.tangem.core.ui.components.RectangleShimmer import com.tangem.core.ui.components.atoms.text.EllipsisText import com.tangem.core.ui.components.atoms.text.TextEllipsis import com.tangem.core.ui.components.icons.identicon.IdentIcon @@ -43,9 +50,42 @@ fun ListItemWithIcon( info: String? = null, subtitleEndOffset: Int = 0, @DrawableRes subtitleIconRes: Int? = null, + isLoading: Boolean = false, +) { + AnimatedContent( + targetState = isLoading, + label = "Recent List Content Animation", + transitionSpec = { fadeIn().togetherWith(fadeOut()) }, + ) { isLoadingState -> + if (isLoadingState) { + ListItemLoading(modifier = modifier) + } else { + ListItemWithIcon( + title = title, + subtitle = subtitle, + onClick = onClick, + info = info, + subtitleEndOffset = subtitleEndOffset, + subtitleIconRes = subtitleIconRes, + modifier = modifier, + ) + } + } +} + +@Composable +private fun ListItemWithIcon( + title: String, + subtitle: String, + onClick: () -> Unit, + modifier: Modifier = Modifier, + info: String? = null, + subtitleEndOffset: Int = 0, + @DrawableRes subtitleIconRes: Int? = null, ) { val hapticFeedback = rememberHapticFeedback(state = title, onAction = onClick) Row( + verticalAlignment = Alignment.CenterVertically, modifier = modifier .fillMaxWidth() .clickable { hapticFeedback() } @@ -55,13 +95,12 @@ fun ListItemWithIcon( address = title, modifier = Modifier .padding(vertical = TangemTheme.dimens.spacing8) - .size(TangemTheme.dimens.size40) - .clip(RoundedCornerShape(TangemTheme.dimens.radius20)), + .size(TangemTheme.dimens.size36) + .clip(RoundedCornerShape(TangemTheme.dimens.radius18)), ) Column( - modifier = Modifier - .padding(vertical = TangemTheme.dimens.spacing10) - .padding(start = TangemTheme.dimens.spacing12), + modifier = Modifier.padding(start = TangemTheme.dimens.spacing12), + verticalArrangement = Arrangement.SpaceBetween, ) { EllipsisText( text = title, @@ -102,6 +141,43 @@ fun ListItemWithIcon( } } +@Composable +private fun ListItemLoading(modifier: Modifier = Modifier) { + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = modifier + .fillMaxWidth() + .padding(horizontal = TangemTheme.dimens.spacing12), + ) { + CircleShimmer( + modifier = Modifier + .padding(vertical = TangemTheme.dimens.spacing8) + .size(TangemTheme.dimens.size36), + ) + Column( + modifier = Modifier + .height(TangemTheme.dimens.size32) + .padding(start = TangemTheme.dimens.spacing12), + verticalArrangement = Arrangement.SpaceBetween, + ) { + RectangleShimmer( + radius = TangemTheme.dimens.radius3, + modifier = Modifier.size( + width = TangemTheme.dimens.spacing70, + height = TangemTheme.dimens.spacing12, + ), + ) + RectangleShimmer( + radius = TangemTheme.dimens.radius3, + modifier = Modifier.size( + width = TangemTheme.dimens.spacing52, + height = TangemTheme.dimens.spacing12, + ), + ) + } + } +} + // region preview @Preview @Composable @@ -115,6 +191,7 @@ private fun ListItemWithIconPreview_Light( subtitleEndOffset = config.subtitleEndOffset, subtitleIconRes = config.iconRes, onClick = {}, + isLoading = config.isLoading, ) } } @@ -131,6 +208,7 @@ private fun ListItemWithIconPreview_Dark( subtitleEndOffset = config.subtitleEndOffset, subtitleIconRes = config.iconRes, onClick = {}, + isLoading = config.isLoading, ) } } @@ -141,6 +219,7 @@ private data class ListItemWithIconPreviewConfig( val info: String? = null, val subtitleEndOffset: Int = 0, val iconRes: Int? = null, + val isLoading: Boolean = false, ) private class ListItemWithIconPreviewProvider : CollectionPreviewParameterProvider( @@ -163,6 +242,14 @@ private class ListItemWithIconPreviewProvider : CollectionPreviewParameterProvid title = "0x34B4492A412D84A6E606288f3Bd714b89135D4dE", subtitle = "Wallet", ), + ListItemWithIconPreviewConfig( + title = "0x34B4492A412D84A6E606288f3Bd714b89135D4dE", + subtitle = "0.000000000000000000000000000000 BTC", + info = "0.0.0000 at 00:00", + subtitleEndOffset = "BTC".length, + iconRes = R.drawable.ic_arrow_down_24, + isLoading = true, + ), ), ) //endregion \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/recipient/SendRecipientContent.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/recipient/SendRecipientContent.kt index eb9b2b544a..f728ff97cb 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/recipient/SendRecipientContent.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/recipient/SendRecipientContent.kt @@ -2,8 +2,8 @@ package com.tangem.features.send.impl.presentation.ui.recipient import androidx.annotation.StringRes import androidx.compose.animation.* -import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding @@ -76,13 +76,13 @@ internal fun SendRecipientContent( listItem( list = wallets, clickIntents = clickIntents, - isLast = recipients.isEmpty(), + isLast = recipients.any { !it.isVisible }, isBalanceHidden = isBalanceHidden, ) listHeaderItem( titleRes = R.string.send_recent_transactions, isVisible = recipients.isNotEmpty() && recipients.first().isVisible, - isFirst = wallets.isEmpty(), + isFirst = wallets.any { !it.isVisible }, ) listItem( list = recipients, @@ -143,20 +143,9 @@ private fun LazyListScope.memoField(memoField: SendTextField.RecipientMemo?, onM } } -@OptIn(ExperimentalFoundationApi::class) private fun LazyListScope.listHeaderItem(@StringRes titleRes: Int, isVisible: Boolean, isFirst: Boolean) { - item( - key = titleRes, - ) { - AnimatedVisibility( - visible = isVisible, - label = "Header Appearance Animation", - enter = slideInVertically() + fadeIn(), - exit = slideOutVertically() + fadeOut(), - modifier = Modifier - .animateItemPlacement() - .animateContentSize(), - ) { + item(key = titleRes) { + AnimateRecentAppearance(isVisible) { val (topPadding, paddingFromTop) = if (isFirst) { TangemTheme.dimens.spacing20 to TangemTheme.dimens.spacing12 } else { @@ -192,7 +181,6 @@ private fun LazyListScope.listHeaderItem(@StringRes titleRes: Int, isVisible: Bo } } -@OptIn(ExperimentalFoundationApi::class) private fun LazyListScope.listItem( list: ImmutableList, clickIntents: SendClickIntents, @@ -206,22 +194,20 @@ private fun LazyListScope.listItem( ) { index -> val item = list[index] val title = item.title.resolveReference() - AnimatedVisibility( - visible = item.isVisible, - label = "Header Appearance Animation", - enter = slideInVertically() + fadeIn(), - exit = slideOutVertically() + fadeOut(), - modifier = Modifier - .animateItemPlacement() - .animateContentSize(), - ) { + AnimateRecentAppearance(item.isVisible) { ListItemWithIcon( title = title, subtitle = if (isBalanceHidden) STARS else item.subtitle.resolveReference(), info = item.timestamp?.resolveReference(), subtitleEndOffset = item.subtitleEndOffset, subtitleIconRes = item.subtitleIconRes, - onClick = { clickIntents.onRecipientAddressValueChange(title, EnterAddressSource.RecentAddress) }, + onClick = { + clickIntents.onRecipientAddressValueChange( + title, + EnterAddressSource.RecentAddress, + ) + }, + isLoading = item.isLoading, modifier = Modifier .then( if (isLast && index == list.lastIndex) { @@ -240,4 +226,22 @@ private fun LazyListScope.listItem( ) } } +} + +@Composable +private fun AnimateRecentAppearance(isVisible: Boolean, content: @Composable () -> Unit) { + AnimatedContent( + targetState = isVisible, + label = "Item Appearance Animation", + transitionSpec = { + (slideInHorizontally() + fadeIn()) + .togetherWith(slideOutVertically() + fadeOut()) + }, + ) { + if (it) { + content() + } else { + Box(modifier = Modifier.fillMaxWidth()) + } + } } \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendViewModel.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendViewModel.kt index c92d4b895d..2a3112371e 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendViewModel.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendViewModel.kt @@ -30,7 +30,6 @@ import com.tangem.domain.transaction.usecase.CreateTransactionUseCase import com.tangem.domain.transaction.usecase.GetFeeUseCase import com.tangem.domain.transaction.usecase.IsFeeApproximateUseCase import com.tangem.domain.transaction.usecase.SendTransactionUseCase -import com.tangem.domain.txhistory.models.TxHistoryItem import com.tangem.domain.txhistory.usecase.GetExplorerTransactionUrlUseCase import com.tangem.domain.txhistory.usecase.GetFixedTxHistoryItemsUseCase import com.tangem.domain.wallets.models.UserWallet @@ -56,7 +55,6 @@ import com.tangem.utils.coroutines.CoroutineDispatcherProvider import com.tangem.utils.coroutines.JobHolder import com.tangem.utils.coroutines.saveIn import dagger.hilt.android.lifecycle.HiltViewModel -import kotlinx.collections.immutable.toPersistentList import kotlinx.coroutines.* import kotlinx.coroutines.flow.* import timber.log.Timber @@ -183,7 +181,6 @@ internal class SendViewModel @Inject constructor( private var balanceJobHolder = JobHolder() private var balanceHidingJobHolder = JobHolder() - private var recipientsJobHolder = JobHolder() private var feeJobHolder = JobHolder() private var addressValidationJobHolder = JobHolder() private var memoValidationJobHolder = JobHolder() @@ -374,34 +371,33 @@ internal class SendViewModel @Inject constructor( } private fun getWalletsAndRecent() { - combine( - flow = getUserWallets().conflate(), - flow2 = getTxHistory().conflate(), - ) { wallets, txHistory -> - uiState = stateFactory.onLoadedRecipientList( - wallets = wallets, - txHistory = txHistory, - ) + getUserWallets() + viewModelScope.launch(dispatchers.main) { + getTxHistory() } - .flowOn(dispatchers.io) - .launchIn(viewModelScope) - .saveIn(recipientsJobHolder) } - private fun getUserWallets(): Flow> { - return getWalletsUseCase() + private fun getUserWallets() { + getWalletsUseCase() + .conflate() .distinctUntilChanged() - .map { userWallets -> + .onEach { userWallets -> coroutineScope { - userWallets - .filterNot { it.walletId == userWalletId || it.isLocked } - .map { wallet -> - async(dispatchers.io) { - wallet.toAvailableWallet() - } - } - }.awaitAll() + runCatching { + userWallets + .filterNot { it.walletId == userWalletId || it.isLocked } + .map { wallet -> + async(dispatchers.io) { wallet.toAvailableWallet() } + }.awaitAll() + }.onSuccess { result -> + uiState = stateFactory.onLoadedWalletsList(wallets = result) + }.onFailure { + uiState = stateFactory.onLoadedWalletsList(wallets = emptyList()) + } + } } + .flowOn(dispatchers.main) + .launchIn(viewModelScope) } private suspend fun UserWallet.toAvailableWallet(): AvailableWallet? { @@ -431,14 +427,12 @@ internal class SendViewModel @Inject constructor( } } - private fun getTxHistory(): Flow> { - return getFixedTxHistoryItemsUseCase( + private suspend fun getTxHistory() { + val txHistoryList = getFixedTxHistoryItemsUseCase.getSync( userWalletId = userWalletId, currency = cryptoCurrency, - ).fold( - ifRight = { it.distinctUntilChanged() }, - ifLeft = { emptyFlow() }, - ) + ).getOrElse { emptyList() } + uiState = stateFactory.onLoadedHistoryList(txHistory = txHistoryList) } private fun onStateActive() { @@ -636,13 +630,9 @@ internal class SendViewModel @Inject constructor( } private fun onEnteredValidAddress(isValidAddress: Boolean, isAddressInWallet: Boolean) { - val recipientState = uiState.recipientState ?: return - val isVisible = isAddressInWallet || !isValidAddress - uiState = uiState.copy( - recipientState = recipientState.copy( - recent = recipientState.recent.map { it.copy(isVisible = isVisible) }.toPersistentList(), - wallets = recipientState.wallets.map { it.copy(isVisible = isVisible) }.toPersistentList(), - ), + uiState = stateFactory.getHiddenRecentListState( + isAddressInWallet = isAddressInWallet, + isValidAddress = isValidAddress, ) } From 5dbe85c826209d9f4caea912c6bf3517bca241bd Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 16 Apr 2024 12:59:45 +0500 Subject: [PATCH 51/67] Updated on 2026-08-14 --- .../presentation/state/fee/custom/EthereumCustomFeeConverter.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/custom/EthereumCustomFeeConverter.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/custom/EthereumCustomFeeConverter.kt index a9bf8e5607..4851533b49 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/custom/EthereumCustomFeeConverter.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/custom/EthereumCustomFeeConverter.kt @@ -48,7 +48,7 @@ internal class EthereumCustomFeeConverter( keyboardActions = KeyboardActions(), ), SendTextField.CustomFee( - value = value.gasPrice.toBigDecimal().movePointLeft(GIGA_DECIMALS).toString(), + value = value.gasPrice.toBigDecimal().movePointLeft(GIGA_DECIMALS).parseBigDecimal(GIGA_DECIMALS), decimals = GIGA_DECIMALS, symbol = ETHEREUM_GAS_UNIT, title = resourceReference(R.string.send_gas_price), From fe23b8ccc319aa90cdb800b5b0c94aed08ee4e72 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 16 Apr 2024 15:44:42 +0300 Subject: [PATCH 52/67] Updated on 2026-08-14 --- .../send/impl/presentation/ui/SendScreen.kt | 57 ++++++++++--------- .../presentation/ui/amount/AmountField.kt | 18 ++++-- 2 files changed, 44 insertions(+), 31 deletions(-) diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/SendScreen.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/SendScreen.kt index 0294fefa3b..caf0a2f4f2 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/SendScreen.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/SendScreen.kt @@ -98,33 +98,36 @@ private fun SendScreenContent(uiState: SendUiState, currentState: SendUiCurrentS AnimatedContentTransitionScope.SlideDirection.End } } - AnimatedContent( - targetState = currentState, - label = "Send Scree Navigation", - modifier = modifier, - transitionSpec = { - lastState = currentState.type.ordinal - slideIntoContainer(towards = direction, animationSpec = tween()) - .togetherWith(slideOutOfContainer(towards = direction, animationSpec = tween())) - }, - ) { state -> - when (state.type) { - SendUiStateType.Amount -> SendAmountContent( - amountState = uiState.amountState, - isBalanceHiding = uiState.isBalanceHidden, - clickIntents = uiState.clickIntents, - ) - SendUiStateType.Recipient -> SendRecipientContent( - uiState = uiState.recipientState, - clickIntents = uiState.clickIntents, - isBalanceHidden = uiState.isBalanceHidden, - ) - SendUiStateType.Fee -> SendSpeedAndFeeContent( - state = uiState.feeState, - clickIntents = uiState.clickIntents, - ) - SendUiStateType.Send -> SendContent(uiState) - else -> Unit + // Box is needed to fix animation with resizing of AnimatedContent + Box(modifier = modifier) { + AnimatedContent( + targetState = currentState, + label = "Send Scree Navigation", + transitionSpec = { + lastState = currentState.type.ordinal + slideIntoContainer(towards = direction, animationSpec = tween()) + .togetherWith(slideOutOfContainer(towards = direction, animationSpec = tween())) + }, + ) { state -> + + when (state.type) { + SendUiStateType.Amount -> SendAmountContent( + amountState = uiState.amountState, + isBalanceHiding = uiState.isBalanceHidden, + clickIntents = uiState.clickIntents, + ) + SendUiStateType.Recipient -> SendRecipientContent( + uiState = uiState.recipientState, + clickIntents = uiState.clickIntents, + isBalanceHidden = uiState.isBalanceHidden, + ) + SendUiStateType.Fee -> SendSpeedAndFeeContent( + state = uiState.feeState, + clickIntents = uiState.clickIntents, + ) + SendUiStateType.Send -> SendContent(uiState) + else -> Unit + } } } } \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/amount/AmountField.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/amount/AmountField.kt index 5aa3ec82ac..bb5b97aa51 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/amount/AmountField.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/amount/AmountField.kt @@ -7,9 +7,7 @@ import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.requiredHeightIn import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.remember +import androidx.compose.runtime.* import androidx.compose.ui.Alignment.Companion.BottomCenter import androidx.compose.ui.Modifier import androidx.compose.ui.focus.FocusRequester @@ -24,6 +22,7 @@ import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.utils.BigDecimalFormatter import com.tangem.core.ui.utils.rememberDecimalFormat import com.tangem.features.send.impl.presentation.state.fields.SendTextField +import kotlinx.coroutines.delay import kotlinx.coroutines.job @Composable @@ -37,6 +36,16 @@ internal fun AmountField(sendField: SendTextField.AmountField, isEnabled: Boolea sendField.cryptoAmount to sendField.value } val requester = remember { FocusRequester() } + var isEnabledProxy by remember { mutableStateOf(isEnabled) } + + // Fix animation from amount screen to summary screen ([REDACTED_TASK_KEY]) + LaunchedEffect(key1 = isEnabled) { + if (isEnabled) { + delay(timeMillis = 700) + } + isEnabledProxy = isEnabled + } + AmountTextField( value = primaryValue, decimals = primaryAmount.decimals, @@ -53,7 +62,7 @@ internal fun AmountField(sendField: SendTextField.AmountField, isEnabled: Boolea color = TangemTheme.colors.text.primary1, textAlign = TextAlign.Center, ), - isEnabled = isEnabled, + isEnabled = isEnabledProxy, isAutoResize = true, modifier = Modifier .focusRequester(requester) @@ -64,6 +73,7 @@ internal fun AmountField(sendField: SendTextField.AmountField, isEnabled: Boolea ) .requiredHeightIn(min = TangemTheme.dimens.size32), ) + LaunchedEffect(key1 = Unit) { this.coroutineContext.job.invokeOnCompletion { requester.requestFocus() From a7a032b65d01d26ed3975688cd1b91805e188a91 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 16 Apr 2024 17:43:17 +0500 Subject: [PATCH 53/67] Updated on 2026-08-14 --- .../data/txhistory/repository/DefaultTxHistoryRepository.kt | 3 +-- .../send/impl/presentation/ui/SendNavigationButtons.kt | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/data/txhistory/src/main/kotlin/com/tangem/data/txhistory/repository/DefaultTxHistoryRepository.kt b/data/txhistory/src/main/kotlin/com/tangem/data/txhistory/repository/DefaultTxHistoryRepository.kt index 64268e3c0c..eada356d1e 100644 --- a/data/txhistory/src/main/kotlin/com/tangem/data/txhistory/repository/DefaultTxHistoryRepository.kt +++ b/data/txhistory/src/main/kotlin/com/tangem/data/txhistory/repository/DefaultTxHistoryRepository.kt @@ -9,7 +9,6 @@ import com.tangem.data.common.cache.CacheRegistry import com.tangem.data.txhistory.repository.paging.TxHistoryPagingSource import com.tangem.datasource.local.txhistory.TxHistoryItemsStore import com.tangem.datasource.local.userwallet.UserWalletsStore -import com.tangem.domain.common.extensions.fromNetworkId import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.tokens.model.Network import com.tangem.domain.txhistory.models.Page @@ -80,7 +79,7 @@ class DefaultTxHistoryRepository( } override suspend fun getTxExploreUrl(userWalletId: UserWalletId, network: Network): String { - val blockchain = Blockchain.fromNetworkId(network.id.value) + val blockchain = Blockchain.fromId(network.id.value) val walletManager = walletManagersFacade.getOrCreateWalletManager( userWalletId = userWalletId, network = network, diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/SendNavigationButtons.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/SendNavigationButtons.kt index 20f2800793..fda14b8a8a 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/SendNavigationButtons.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/SendNavigationButtons.kt @@ -185,7 +185,7 @@ private fun SendDoneButtons( exit = slideOutVertically().plus(fadeOut()), label = "Animate show sent state buttons", ) { - Row(modifier = Modifier.padding(TangemTheme.dimens.spacing12)) { + Row(modifier = Modifier.padding(bottom = TangemTheme.dimens.spacing12)) { SecondaryButtonIconStart( text = stringResource(id = R.string.common_explore), iconResId = R.drawable.ic_web_24, From 9fe8c6bedd0efdfc4718f1e9e73c580baaa80531 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 16 Apr 2024 14:31:04 +0100 Subject: [PATCH 54/67] Updated on 2026-08-14 --- .../java/com/tangem/tap/proxy/TransactionManagerImpl.kt | 6 +++--- gradle/dependencies.toml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/proxy/TransactionManagerImpl.kt b/app/src/main/java/com/tangem/tap/proxy/TransactionManagerImpl.kt index 4c7a8f4461..8b3e444c59 100644 --- a/app/src/main/java/com/tangem/tap/proxy/TransactionManagerImpl.kt +++ b/app/src/main/java/com/tangem/tap/proxy/TransactionManagerImpl.kt @@ -9,7 +9,7 @@ import com.tangem.blockchain.blockchains.cosmos.CosmosTransactionExtras import com.tangem.blockchain.blockchains.ethereum.EthereumTransactionExtras import com.tangem.blockchain.blockchains.ethereum.EthereumWalletManager import com.tangem.blockchain.blockchains.hedera.HederaTransactionBuilder -import com.tangem.blockchain.blockchains.optimism.OptimismWalletManager +import com.tangem.blockchain.blockchains.optimism.EthereumOptimisticRollupWalletManager import com.tangem.blockchain.blockchains.stellar.StellarMemo import com.tangem.blockchain.blockchains.stellar.StellarTransactionExtras import com.tangem.blockchain.blockchains.ton.TonTransactionExtras @@ -170,7 +170,7 @@ class TransactionManagerImpl( val blockchain = requireNotNull(Blockchain.fromNetworkId(networkId)) { "blockchain not found" } val walletManager = getActualWalletManager(blockchain, derivationPath) if (walletManager is EthereumWalletManager) { - if (walletManager is OptimismWalletManager) { + if (walletManager is EthereumOptimisticRollupWalletManager) { return getFeeForOptimismBlockchain( walletManager = walletManager, amount = createAmount(amountToSend, currencyToSend, blockchain), @@ -285,7 +285,7 @@ class TransactionManagerImpl( } private suspend fun getFeeForOptimismBlockchain( - walletManager: OptimismWalletManager, + walletManager: EthereumOptimisticRollupWalletManager, amount: Amount, destinationAddress: String, data: String?, diff --git a/gradle/dependencies.toml b/gradle/dependencies.toml index 8fe2844437..702d001217 100644 --- a/gradle/dependencies.toml +++ b/gradle/dependencies.toml @@ -85,7 +85,7 @@ leakcanary = "2.13" # endregion Other libraries # region Tangem -tangemBlockchainSdk = "release-app_5.9-589" +tangemBlockchainSdk = "release-app_5.9-596" #tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds tangemCardSdk = "release-app_5.9-343" #tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^ From e35cd41fd2ccd2b6d1d0f0f07472960b3056dafa Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 16 Apr 2024 15:28:55 +0100 Subject: [PATCH 55/67] Updated on 2026-08-14 --- .../tangem/tap/di/domain/TokensDomainModule.kt | 2 +- .../tap/domain/scanCard/UseCaseScanProcessor.kt | 16 +++++++++++++--- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/di/domain/TokensDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/TokensDomainModule.kt index e4cf06239e..81d84db2d2 100644 --- a/app/src/main/java/com/tangem/tap/di/domain/TokensDomainModule.kt +++ b/app/src/main/java/com/tangem/tap/di/domain/TokensDomainModule.kt @@ -16,7 +16,7 @@ import dagger.hilt.android.scopes.ViewModelScoped @Module @InstallIn(ViewModelComponent::class) -@Suppress("TooManyFunctions") +@Suppress("TooManyFunctions", "LargeClass") internal object TokensDomainModule { @Provides diff --git a/app/src/main/java/com/tangem/tap/domain/scanCard/UseCaseScanProcessor.kt b/app/src/main/java/com/tangem/tap/domain/scanCard/UseCaseScanProcessor.kt index 9578e1ffe1..ed777bd666 100644 --- a/app/src/main/java/com/tangem/tap/domain/scanCard/UseCaseScanProcessor.kt +++ b/app/src/main/java/com/tangem/tap/domain/scanCard/UseCaseScanProcessor.kt @@ -54,7 +54,11 @@ internal object UseCaseScanProcessor { ) = progressScope(onProgressStateChange) { val scanCardUseCase = store.inject(DaggerGraphState::scanCardUseCase) val chains = buildList { - add(FailedScansCounterChain(UseCaseScanProcessor::showMaxUnsuccessfulScansReachedDialog)) + add( + FailedScansCounterChain( + { showMaxUnsuccessfulScansReachedDialog(analyticsSource) }, + ), + ) add(AnalyticsChain(Basic.CardWasScanned(analyticsSource))) add(DisclaimerChain(store, disclaimerWillShow)) add(CheckForOnboardingChain(store, store.state.globalState.tapWalletManager)) @@ -66,8 +70,14 @@ internal object UseCaseScanProcessor { ) } - private fun showMaxUnsuccessfulScansReachedDialog() { - store.dispatchDialogShow(StateDialog.ScanFailsDialog) + private fun showMaxUnsuccessfulScansReachedDialog(source: AnalyticsParam.ScreensSources) { + val scanFailsSource = when (source) { + is AnalyticsParam.ScreensSources.SignIn -> StateDialog.ScanFailsSource.SIGN_IN + is AnalyticsParam.ScreensSources.Settings -> StateDialog.ScanFailsSource.SETTINGS + is AnalyticsParam.ScreensSources.Intro -> StateDialog.ScanFailsSource.INTRO + else -> StateDialog.ScanFailsSource.MAIN + } + store.dispatchDialogShow(StateDialog.ScanFailsDialog(scanFailsSource)) } private suspend fun proceedWithException( From cec4c2d637436a2fc02a251a12384a6594fa6e18 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 16 Apr 2024 16:28:48 +0100 Subject: [PATCH 56/67] Updated on 2026-08-14 --- app/src/main/assets/tangem-app-config | 2 +- .../wallet/presentation/wallet/domain/BackupValidator.kt | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/app/src/main/assets/tangem-app-config b/app/src/main/assets/tangem-app-config index 980cc4cb2f..367aa6465e 160000 --- a/app/src/main/assets/tangem-app-config +++ b/app/src/main/assets/tangem-app-config @@ -1 +1 @@ -Subproject commit 980cc4cb2fb9ba44cd6798191ae7d208e4f7f74c +Subproject commit 367aa6465ec6ee7fdccce6cc7d7abeed5aad1111 diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/BackupValidator.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/BackupValidator.kt index caa4c9fd87..4153d69e68 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/BackupValidator.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/BackupValidator.kt @@ -37,6 +37,7 @@ class BackupValidator @Inject constructor() { private fun validateBackupStatus(cardDTO: CardDTO): Boolean { val backupStatus = cardDTO.backupStatus - return backupStatus != null && backupStatus !is CardDTO.BackupStatus.CardLinked + backupStatus ?: return true // for card with null backup status, validation should always returns true + return backupStatus !is CardDTO.BackupStatus.CardLinked } } \ No newline at end of file From dca4c954b4e3172590c0f778e8b0b11c3dbf5104 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 16 Apr 2024 19:44:27 +0500 Subject: [PATCH 57/67] Updated on 2026-08-14 --- .../src/main/assets/configs/feature_toggles_config.json | 2 +- .../presentation/state/confirm/SendNotificationFactory.kt | 5 ++--- .../presentation/state/previewdata/SendClickIntentsStub.kt | 3 ++- .../send/impl/presentation/viewmodel/SendClickIntents.kt | 3 ++- .../send/impl/presentation/viewmodel/SendViewModel.kt | 6 ++++-- gradle/dependencies.toml | 2 +- 6 files changed, 12 insertions(+), 9 deletions(-) diff --git a/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json b/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json index 7a8e690d21..84088baede 100644 --- a/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json +++ b/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json @@ -9,7 +9,7 @@ }, { "name": "REDESIGNED_SEND_SCREEN_ENABLED", - "version": "undefined" + "version": "5.9.0" }, { "name": "GENERAL_USER_WALLETS_LIST_MANAGER_ENABLED", diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/confirm/SendNotificationFactory.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/confirm/SendNotificationFactory.kt index 68d334e0ae..0d147eb6fd 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/confirm/SendNotificationFactory.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/confirm/SendNotificationFactory.kt @@ -167,9 +167,8 @@ internal class SendNotificationFactory( cryptoCurrency = cryptoCurrency, ), onConfirmClick = { - val reduceTo = utxoLimit.maxAmount.toPlainString() clickIntents.onAmountReduceClick( - reduceTo, + utxoLimit.maxAmount, SendNotification.Error.TransactionLimitError::class.java, ) }, @@ -223,7 +222,7 @@ internal class SendNotificationFactory( SendNotification.Warning.HighFeeError( amount = threshold.toPlainString(), onConfirmClick = { - val reduceTo = sendAmount.minus(threshold).toPlainString() + val reduceTo = sendAmount.minus(threshold) clickIntents.onAmountReduceClick(reduceTo, SendNotification.Warning.HighFeeError::class.java) }, onCloseClick = { diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/previewdata/SendClickIntentsStub.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/previewdata/SendClickIntentsStub.kt index 67d5b07642..92be46efa2 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/previewdata/SendClickIntentsStub.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/previewdata/SendClickIntentsStub.kt @@ -6,6 +6,7 @@ import com.tangem.features.send.impl.presentation.analytics.EnterAddressSource import com.tangem.features.send.impl.presentation.state.SendNotification import com.tangem.features.send.impl.presentation.state.fee.FeeType import com.tangem.features.send.impl.presentation.viewmodel.SendClickIntents +import java.math.BigDecimal @Suppress("TooManyFunctions") internal object SendClickIntentsStub : SendClickIntents { @@ -57,7 +58,7 @@ internal object SendClickIntentsStub : SendClickIntents { override fun onShareClick() {} - override fun onAmountReduceClick(reducedAmount: String, clazz: Class) {} + override fun onAmountReduceClick(reducedAmount: BigDecimal, clazz: Class) {} override fun onNotificationCancel(clazz: Class) {} } \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendClickIntents.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendClickIntents.kt index 14ed1562af..9db5547b20 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendClickIntents.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendClickIntents.kt @@ -5,6 +5,7 @@ import com.tangem.domain.wallets.models.UserWalletId import com.tangem.features.send.impl.presentation.analytics.EnterAddressSource import com.tangem.features.send.impl.presentation.state.SendNotification import com.tangem.features.send.impl.presentation.state.fee.FeeType +import java.math.BigDecimal @Suppress("TooManyFunctions") internal interface SendClickIntents { @@ -64,7 +65,7 @@ internal interface SendClickIntents { fun onShareClick() - fun onAmountReduceClick(reducedAmount: String, clazz: Class) + fun onAmountReduceClick(reducedAmount: BigDecimal, clazz: Class) fun onNotificationCancel(clazz: Class) // endregion diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendViewModel.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendViewModel.kt index 2a3112371e..2d397422cf 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendViewModel.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendViewModel.kt @@ -10,6 +10,7 @@ import arrow.core.getOrElse import com.tangem.blockchain.common.TransactionData import com.tangem.blockchain.common.transaction.TransactionFee import com.tangem.core.analytics.api.AnalyticsEventHandler +import com.tangem.core.ui.utils.parseBigDecimal import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase @@ -58,6 +59,7 @@ import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.* import kotlinx.coroutines.flow.* import timber.log.Timber +import java.math.BigDecimal import java.util.Locale import javax.inject.Inject import kotlin.properties.Delegates @@ -785,8 +787,8 @@ internal class SendViewModel @Inject constructor( analyticsEventHandler.send(SendAnalyticEvents.ShareButtonClicked) } - override fun onAmountReduceClick(reducedAmount: String, clazz: Class) { - uiState = amountStateFactory.getOnAmountValueChange(reducedAmount) + override fun onAmountReduceClick(reducedAmount: BigDecimal, clazz: Class) { + uiState = amountStateFactory.getOnAmountValueChange(reducedAmount.parseBigDecimal(cryptoCurrency.decimals)) uiState = sendNotificationFactory.dismissNotificationState(clazz) } diff --git a/gradle/dependencies.toml b/gradle/dependencies.toml index 702d001217..48a3bcb3df 100644 --- a/gradle/dependencies.toml +++ b/gradle/dependencies.toml @@ -85,7 +85,7 @@ leakcanary = "2.13" # endregion Other libraries # region Tangem -tangemBlockchainSdk = "release-app_5.9-596" +tangemBlockchainSdk = "release-app_5.9-599" #tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds tangemCardSdk = "release-app_5.9-343" #tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^ From f8b66b963707baf8d8fbebcb46a0038786e7f1f8 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 16 Apr 2024 18:25:31 +0100 Subject: [PATCH 58/67] Updated on 2026-08-14 --- gradle/dependencies.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/dependencies.toml b/gradle/dependencies.toml index 48a3bcb3df..56d6351be9 100644 --- a/gradle/dependencies.toml +++ b/gradle/dependencies.toml @@ -85,7 +85,7 @@ leakcanary = "2.13" # endregion Other libraries # region Tangem -tangemBlockchainSdk = "release-app_5.9-599" +tangemBlockchainSdk = "release-app_5.9-600" #tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds tangemCardSdk = "release-app_5.9-343" #tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^ From 10798c0ff37eef10fe30e543202004bd85dde55a Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 16 Apr 2024 16:28:48 +0100 Subject: [PATCH 59/67] Updated on 2026-08-14 --- app/src/main/assets/tangem-app-config | 2 +- .../wallet/presentation/wallet/domain/BackupValidator.kt | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/app/src/main/assets/tangem-app-config b/app/src/main/assets/tangem-app-config index 980cc4cb2f..367aa6465e 160000 --- a/app/src/main/assets/tangem-app-config +++ b/app/src/main/assets/tangem-app-config @@ -1 +1 @@ -Subproject commit 980cc4cb2fb9ba44cd6798191ae7d208e4f7f74c +Subproject commit 367aa6465ec6ee7fdccce6cc7d7abeed5aad1111 diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/BackupValidator.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/BackupValidator.kt index caa4c9fd87..4153d69e68 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/BackupValidator.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/BackupValidator.kt @@ -37,6 +37,7 @@ class BackupValidator @Inject constructor() { private fun validateBackupStatus(cardDTO: CardDTO): Boolean { val backupStatus = cardDTO.backupStatus - return backupStatus != null && backupStatus !is CardDTO.BackupStatus.CardLinked + backupStatus ?: return true // for card with null backup status, validation should always returns true + return backupStatus !is CardDTO.BackupStatus.CardLinked } } \ No newline at end of file From c492f4b9d3bcc02226cb22b92f6fbb309980c5fd Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 16 Apr 2024 19:44:27 +0500 Subject: [PATCH 60/67] Updated on 2026-08-14 --- .../src/main/assets/configs/feature_toggles_config.json | 2 +- .../presentation/state/confirm/SendNotificationFactory.kt | 5 ++--- .../presentation/state/previewdata/SendClickIntentsStub.kt | 3 ++- .../send/impl/presentation/viewmodel/SendClickIntents.kt | 3 ++- .../send/impl/presentation/viewmodel/SendViewModel.kt | 6 ++++-- gradle/dependencies.toml | 2 +- 6 files changed, 12 insertions(+), 9 deletions(-) diff --git a/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json b/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json index 7a8e690d21..84088baede 100644 --- a/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json +++ b/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json @@ -9,7 +9,7 @@ }, { "name": "REDESIGNED_SEND_SCREEN_ENABLED", - "version": "undefined" + "version": "5.9.0" }, { "name": "GENERAL_USER_WALLETS_LIST_MANAGER_ENABLED", diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/confirm/SendNotificationFactory.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/confirm/SendNotificationFactory.kt index 68d334e0ae..0d147eb6fd 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/confirm/SendNotificationFactory.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/confirm/SendNotificationFactory.kt @@ -167,9 +167,8 @@ internal class SendNotificationFactory( cryptoCurrency = cryptoCurrency, ), onConfirmClick = { - val reduceTo = utxoLimit.maxAmount.toPlainString() clickIntents.onAmountReduceClick( - reduceTo, + utxoLimit.maxAmount, SendNotification.Error.TransactionLimitError::class.java, ) }, @@ -223,7 +222,7 @@ internal class SendNotificationFactory( SendNotification.Warning.HighFeeError( amount = threshold.toPlainString(), onConfirmClick = { - val reduceTo = sendAmount.minus(threshold).toPlainString() + val reduceTo = sendAmount.minus(threshold) clickIntents.onAmountReduceClick(reduceTo, SendNotification.Warning.HighFeeError::class.java) }, onCloseClick = { diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/previewdata/SendClickIntentsStub.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/previewdata/SendClickIntentsStub.kt index 67d5b07642..92be46efa2 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/previewdata/SendClickIntentsStub.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/previewdata/SendClickIntentsStub.kt @@ -6,6 +6,7 @@ import com.tangem.features.send.impl.presentation.analytics.EnterAddressSource import com.tangem.features.send.impl.presentation.state.SendNotification import com.tangem.features.send.impl.presentation.state.fee.FeeType import com.tangem.features.send.impl.presentation.viewmodel.SendClickIntents +import java.math.BigDecimal @Suppress("TooManyFunctions") internal object SendClickIntentsStub : SendClickIntents { @@ -57,7 +58,7 @@ internal object SendClickIntentsStub : SendClickIntents { override fun onShareClick() {} - override fun onAmountReduceClick(reducedAmount: String, clazz: Class) {} + override fun onAmountReduceClick(reducedAmount: BigDecimal, clazz: Class) {} override fun onNotificationCancel(clazz: Class) {} } \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendClickIntents.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendClickIntents.kt index 14ed1562af..9db5547b20 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendClickIntents.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendClickIntents.kt @@ -5,6 +5,7 @@ import com.tangem.domain.wallets.models.UserWalletId import com.tangem.features.send.impl.presentation.analytics.EnterAddressSource import com.tangem.features.send.impl.presentation.state.SendNotification import com.tangem.features.send.impl.presentation.state.fee.FeeType +import java.math.BigDecimal @Suppress("TooManyFunctions") internal interface SendClickIntents { @@ -64,7 +65,7 @@ internal interface SendClickIntents { fun onShareClick() - fun onAmountReduceClick(reducedAmount: String, clazz: Class) + fun onAmountReduceClick(reducedAmount: BigDecimal, clazz: Class) fun onNotificationCancel(clazz: Class) // endregion diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendViewModel.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendViewModel.kt index 2a3112371e..2d397422cf 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendViewModel.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendViewModel.kt @@ -10,6 +10,7 @@ import arrow.core.getOrElse import com.tangem.blockchain.common.TransactionData import com.tangem.blockchain.common.transaction.TransactionFee import com.tangem.core.analytics.api.AnalyticsEventHandler +import com.tangem.core.ui.utils.parseBigDecimal import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase @@ -58,6 +59,7 @@ import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.* import kotlinx.coroutines.flow.* import timber.log.Timber +import java.math.BigDecimal import java.util.Locale import javax.inject.Inject import kotlin.properties.Delegates @@ -785,8 +787,8 @@ internal class SendViewModel @Inject constructor( analyticsEventHandler.send(SendAnalyticEvents.ShareButtonClicked) } - override fun onAmountReduceClick(reducedAmount: String, clazz: Class) { - uiState = amountStateFactory.getOnAmountValueChange(reducedAmount) + override fun onAmountReduceClick(reducedAmount: BigDecimal, clazz: Class) { + uiState = amountStateFactory.getOnAmountValueChange(reducedAmount.parseBigDecimal(cryptoCurrency.decimals)) uiState = sendNotificationFactory.dismissNotificationState(clazz) } diff --git a/gradle/dependencies.toml b/gradle/dependencies.toml index 702d001217..48a3bcb3df 100644 --- a/gradle/dependencies.toml +++ b/gradle/dependencies.toml @@ -85,7 +85,7 @@ leakcanary = "2.13" # endregion Other libraries # region Tangem -tangemBlockchainSdk = "release-app_5.9-596" +tangemBlockchainSdk = "release-app_5.9-599" #tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds tangemCardSdk = "release-app_5.9-343" #tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^ From b5079db1addd0ede402d2f0efe22323cec7e9504 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 16 Apr 2024 18:25:31 +0100 Subject: [PATCH 61/67] Updated on 2026-08-14 --- gradle/dependencies.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/dependencies.toml b/gradle/dependencies.toml index 48a3bcb3df..56d6351be9 100644 --- a/gradle/dependencies.toml +++ b/gradle/dependencies.toml @@ -85,7 +85,7 @@ leakcanary = "2.13" # endregion Other libraries # region Tangem -tangemBlockchainSdk = "release-app_5.9-599" +tangemBlockchainSdk = "release-app_5.9-600" #tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds tangemCardSdk = "release-app_5.9-343" #tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^ From f117e5a2469fa46b74ebf882e947e16acf0a2c06 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 17 Apr 2024 10:42:01 +0300 Subject: [PATCH 62/67] Updated on 2026-08-14 --- core/res/src/main/res/values-ru/strings.xml | 5 ++-- core/res/src/main/res/values/strings.xml | 3 ++- .../tokendetails/TokenDetailsPreviewData.kt | 15 ++++++++++++ .../tokendetails/state/TokenInfoBlockState.kt | 2 +- .../TokenDetailsSkeletonStateConverter.kt | 6 ++++- .../ui/components/TokenInfoBlock.kt | 24 +++++++++++++------ tangem-android-tools | 2 +- 7 files changed, 44 insertions(+), 13 deletions(-) diff --git a/core/res/src/main/res/values-ru/strings.xml b/core/res/src/main/res/values-ru/strings.xml index b4bd309592..5d25d41f8e 100644 --- a/core/res/src/main/res/values-ru/strings.xml +++ b/core/res/src/main/res/values-ru/strings.xml @@ -427,7 +427,7 @@ Уже содержится в введенном адресе Вычесть Недостаточно средств для покрытия комиссии сети. Вычесть недостающую сумму для покрытия комиссии из отправляемой суммы? - Сумма комиссии в %@ раз превышает рекомендованную. Убедитесь, что указанная комиссия верна. + Сумма комиссии в %s раз превышает рекомендованную. Убедитесь, что указанная комиссия верна. Вы указали комиссию ниже рекомендуемой, это может привести к задержке исполнения вашей транзакции. Продолжить? Причина: %1$s\nКод: %2$s Транзакция не выполнена @@ -537,9 +537,9 @@ Балансы скрыты Балансы показаны Отменить + Выбранная операция в данный момент недоступна. Попробуйте позже. В данный момент покупка монеты %s недоступна. Но мы работаем над её добавлением. У вас нет средств для отправки. Пополните счет, чтобы иметь возможность отправить с него средства. - Выбранная операция в данный момент недоступна. Попробуйте позже. Обмен %s не доступен. Но мы работаем над его добавлением. В данный момент продажа монеты %s недоступна. Но мы работаем над её добавлением. Сгенерировать XPUB @@ -548,6 +548,7 @@ Скрыть %s Скрыть токен %1$s токен в сети %%image%% %2$s + Токен в сети %%image%% %1$s Токен %1$s является основной валютой в сети %2$s и не может быть скрыт до тех пор, пока у вас в списке есть другие токены этой сети. Невозможно скрыть %s Обменивайте этот токен на другие с %1$s комиссии за обслуживание с %2$s по %3$s февраля. diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml index 9310a0153a..a099c577d4 100644 --- a/core/res/src/main/res/values/strings.xml +++ b/core/res/src/main/res/values/strings.xml @@ -534,9 +534,9 @@ Balances hidden Balances shown Undo + This operation is currently unavailable. Please try again later. The purchase of the %s is currently unavailable. But we are working on adding it. You do not have funds to send. Top up your account to be able to send funds from it. - This operation is currently unavailable. Please try again later. %s swap is not available. But we are working on adding it. Sell of the %s coin is currently unavailable. But we are working on adding it. Generate XPUB @@ -545,6 +545,7 @@ Hide %s Hide token %1$s token in %%image%% %2$s network + Token in %%image%% %1$s network The %1$s token is the main currency on the %2$s network and cannot be hidden as long as you have other tokens on this network in the list. Unable to hide %s Exchange this token for another at %1$s service fees from February %2$s-%3$s. diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/TokenDetailsPreviewData.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/TokenDetailsPreviewData.kt index 77f27a4cac..69d878168f 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/TokenDetailsPreviewData.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/TokenDetailsPreviewData.kt @@ -57,6 +57,21 @@ internal object TokenDetailsPreviewData { ), ) + val tokenInfoBlockStateWithLongNameNoStandard = TokenInfoBlockState( + name = "Tether (USDT) with long name test", + iconState = TokenInfoBlockState.IconState.TokenIcon( + url = "https://s3.eu-central-1.amazonaws.com/tangem.api/coins/large/stellar.png", + fallbackTint = Color.Cyan, + fallbackBackground = Color.Blue, + isGrayscale = false, + ), + currency = TokenInfoBlockState.Currency.Token( + standardName = null, + networkIcon = R.drawable.img_shibarium_22, + networkName = "Shibarium", + ), + ) + val tokenInfoBlockState = TokenInfoBlockState( name = "Tether USDT", iconState = TokenInfoBlockState.IconState.CustomTokenIcon( diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/TokenInfoBlockState.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/TokenInfoBlockState.kt index 7c1e1eda2b..f0ec9e8d43 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/TokenInfoBlockState.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/TokenInfoBlockState.kt @@ -19,7 +19,7 @@ internal data class TokenInfoBlockState( * @param networkIcon - token's network icon. */ data class Token( - val standardName: String, + val standardName: String?, val networkName: String, @DrawableRes val networkIcon: Int, ) : Currency() diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsSkeletonStateConverter.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsSkeletonStateConverter.kt index dbc7ff632b..163a1516ae 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsSkeletonStateConverter.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsSkeletonStateConverter.kt @@ -8,6 +8,7 @@ import com.tangem.core.ui.extensions.networkIconResId import com.tangem.core.ui.extensions.resourceReference import com.tangem.core.ui.res.TangemTheme import com.tangem.domain.tokens.model.CryptoCurrency +import com.tangem.domain.tokens.model.Network import com.tangem.feature.tokendetails.presentation.tokendetails.state.* import com.tangem.feature.tokendetails.presentation.tokendetails.state.components.TokenDetailsActionButton import com.tangem.feature.tokendetails.presentation.tokendetails.state.components.TokenDetailsPullToRefreshConfig @@ -40,7 +41,7 @@ internal class TokenDetailsSkeletonStateConverter( currency = when (value) { is CryptoCurrency.Coin -> TokenInfoBlockState.Currency.Native is CryptoCurrency.Token -> TokenInfoBlockState.Currency.Token( - standardName = value.network.standardType.name, + standardName = value.network.standardType.getSpecifiedNameOrNull(), networkName = value.network.name, networkIcon = value.networkIconResId, ) @@ -65,6 +66,9 @@ internal class TokenDetailsSkeletonStateConverter( ) } + private fun Network.StandardType.getSpecifiedNameOrNull(): String? = + name.takeIf { this !is Network.StandardType.Unspecified } + private fun createMenu(cryptoCurrency: CryptoCurrency): TokenDetailsAppBarMenuConfig = TokenDetailsAppBarMenuConfig( items = buildList { if (featureToggles.isGenerateXPubEnabled() && isBitcoin(cryptoCurrency.network.id.value)) { diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/TokenInfoBlock.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/TokenInfoBlock.kt index 3254286ed2..3982a8dc21 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/TokenInfoBlock.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/TokenInfoBlock.kt @@ -105,13 +105,22 @@ private const val SEPARATOR = " %image% " @Composable private fun extractNetwork(tokenCurrency: TokenInfoBlockState.Currency.Token): ExtractedTokenNetworkText { - val splitString = stringResource( - id = R.string.token_details_token_type_subtitle, - formatArgs = arrayOf( - tokenCurrency.standardName, - tokenCurrency.networkName, - ), - ).split(SEPARATOR) + val splitString = if (tokenCurrency.standardName != null) { + stringResource( + id = R.string.token_details_token_type_subtitle, + formatArgs = arrayOf( + tokenCurrency.standardName, + tokenCurrency.networkName, + ), + ).split(SEPARATOR) + } else { + stringResource( + id = R.string.token_details_token_type_subtitle_no_standard, + formatArgs = arrayOf( + tokenCurrency.networkName, + ), + ).split(SEPARATOR) + } return remember(splitString) { ExtractedTokenNetworkText( @@ -153,5 +162,6 @@ private class TokenInfoStateProvider : CollectionPreviewParameterProvider Date: Wed, 17 Apr 2024 13:33:57 +0500 Subject: [PATCH 63/67] Updated on 2026-08-14 --- .../DefaultQrScanningEventsRepository.kt | 19 ++++++++++--------- .../DefaultQrScanningEventsRepositoryTest.kt | 10 ++++++++++ 2 files changed, 20 insertions(+), 9 deletions(-) diff --git a/data/qr-scanning/src/main/java/com/tangem/data/qrscanning/repository/DefaultQrScanningEventsRepository.kt b/data/qr-scanning/src/main/java/com/tangem/data/qrscanning/repository/DefaultQrScanningEventsRepository.kt index 87729e8bc4..994539366b 100644 --- a/data/qr-scanning/src/main/java/com/tangem/data/qrscanning/repository/DefaultQrScanningEventsRepository.kt +++ b/data/qr-scanning/src/main/java/com/tangem/data/qrscanning/repository/DefaultQrScanningEventsRepository.kt @@ -52,16 +52,17 @@ internal class DefaultQrScanningEventsRepository : QrScanningEventsRepository { result.memo = URLDecoder.decode(it.value, "UTF-8") } Parameter.Address -> { + // If 'address' parameter is exists, then currency must be TOKEN. + val tokenCurrency = cryptoCurrency as? CryptoCurrency.Token ?: return QrResult() + // Overrides destination address for token transfers (ERC-681) - if (cryptoCurrency is CryptoCurrency.Token) { - // `address` parameter is used only if the contract address, encoded in the QR, - // matches the contract address of the token. - // Otherwise, the scanned string is likely malformed, and we stop the entire parsing routine - if (cryptoCurrency.contractAddress.equals(address, ignoreCase = true)) { - result.address = it.value - } else { - return QrResult() - } + // `address` parameter is used only if the contract address, encoded in the QR, + // matches the contract address of the token. + // Otherwise, the scanned string is likely malformed, and we stop the entire parsing routin + if (tokenCurrency.contractAddress.equals(address, ignoreCase = true)) { + result.address = it.value + } else { + return QrResult() } } Parameter.Value, diff --git a/data/qr-scanning/src/test/java/com/tangem/data/qrscanning/DefaultQrScanningEventsRepositoryTest.kt b/data/qr-scanning/src/test/java/com/tangem/data/qrscanning/DefaultQrScanningEventsRepositoryTest.kt index ad1f6244a7..13d49ce302 100644 --- a/data/qr-scanning/src/test/java/com/tangem/data/qrscanning/DefaultQrScanningEventsRepositoryTest.kt +++ b/data/qr-scanning/src/test/java/com/tangem/data/qrscanning/DefaultQrScanningEventsRepositoryTest.kt @@ -232,11 +232,21 @@ internal class DefaultQrScanningEventsRepositoryTest { QrResult(address = address2, amount = BigDecimal("2300")), tokenCryptoCurrency, ) + positiveCase( + "$address4?$addressParam=$addressParamValue", + QrResult(address = addressParamValue), + tokenCryptoCurrency, + ) negativeCase( "$address2?$someParam=$someParamValue&$amountParam=$amountParamValue", QrResult(address = address2, amount = BigDecimal("123.123"), memo = memoParamValueUtf8), tokenCryptoCurrency, ) + negativeCase( + "$address4?$addressParam=$addressParamValue", + QrResult(address = addressParamValue), + cryptoCurrency, + ) } private fun positiveCase(input: String, expected: QrResult, cryptoCurrency: CryptoCurrency) { From 741b6702cee048a9285e4ca9df207973bfcbe24e Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 17 Apr 2024 12:04:24 +0500 Subject: [PATCH 64/67] Updated on 2026-08-14 --- .../tangem/features/send/impl/presentation/SendFragment.kt | 4 ++-- .../send/impl/presentation/viewmodel/SendViewModel.kt | 6 ++++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/SendFragment.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/SendFragment.kt index cebe88712f..64e8f1e992 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/SendFragment.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/SendFragment.kt @@ -92,9 +92,9 @@ internal class SendFragment : ComposeFragment() { delay(QR_SCAN_DELAY) // Delayed launch is needed in order for the UI to be drawn and to process the sent events. - // If do not use the delay, then etAmount error field is not displayed when + // If do not use the delay, then error field is not displayed when // inserting an incorrect amount by shareUri - viewModel.onRecipientAddressScanned(it) + viewModel.onQrCodeScanned(it) } } } diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendViewModel.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendViewModel.kt index 2d397422cf..ffad3f3633 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendViewModel.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendViewModel.kt @@ -569,12 +569,14 @@ internal class SendViewModel @Inject constructor( // endregion // region recipient state clicks - fun onRecipientAddressScanned(address: String) { + fun onQrCodeScanned(address: String) { viewModelScope.launch(dispatchers.main) { parseQrCodeUseCase(address, cryptoCurrency).fold( ifRight = { parsedCode -> onRecipientAddressValueChange(parsedCode.address, EnterAddressSource.QRCode) - parsedCode.amount?.let { onAmountValueChange(it.toPlainString()) } + parsedCode.amount?.let { + onAmountValueChange(it.parseBigDecimal(decimals = cryptoCurrency.decimals)) + } parsedCode.memo?.let { onRecipientMemoValueChange(it) } }, ifLeft = { From 626e2764d308dc91f388f6ec82fa28d7b5ac404a Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 16 Apr 2024 23:08:32 +0500 Subject: [PATCH 65/67] Updated on 2026-08-14 --- .../core/ui/utils/DecimalFormatterExt.kt | 33 +++++++++++++++++++ .../DefaultQrScanningEventsRepository.kt | 5 +-- 2 files changed, 36 insertions(+), 2 deletions(-) diff --git a/core/ui/src/main/java/com/tangem/core/ui/utils/DecimalFormatterExt.kt b/core/ui/src/main/java/com/tangem/core/ui/utils/DecimalFormatterExt.kt index 7db27cf7f4..b5a21b53f7 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/utils/DecimalFormatterExt.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/utils/DecimalFormatterExt.kt @@ -11,6 +11,8 @@ import java.util.Locale private const val TEXT_CHUNK_THOUSAND = 3 private const val POINT_SEPARATOR = '.' +private const val COMMA_SEPARATOR = ',' +const val DECIMAL_SEPARATOR_LIMIT = 1 @Composable fun rememberDecimalFormat(): DecimalFormat { @@ -152,6 +154,37 @@ fun BigDecimal.parseBigDecimal(decimals: Int, roundingMode: RoundingMode = Round } } +/** + * Universal amount string parser to [BigDecimal] + * Able to parse values with only ONE separator, assuming separator is COMMA. + * Otherwise returns null. + */ +fun String.parseBigDecimalOrNull() = runCatching { + // Filtering value containing more than one either grouping or decimal separator. + // We assume there will be only decimal separator, otherwise parsing will fail. + if (count { !it.isDigit() } > DECIMAL_SEPARATOR_LIMIT) return null + + // An attempt to parse value with POINT decimal separator + val parsed = this.toBigDecimalOrNull() + + if (parsed == null) { + // If parsing with POINT separator fails trying to parse with COMMA separator + val decimalFormatSymbol = DecimalFormatSymbols().apply { + decimalSeparator = COMMA_SEPARATOR + } + val decimalFormat = DecimalFormat().apply { + decimalFormatSymbols = decimalFormatSymbol + isParseBigDecimal = true + } + + // Return either number or null if fails + decimalFormat.parse(this) as? BigDecimal + } else { + // If parsing with POINT separator succeeds return number + parsed + } +}.getOrNull() + private fun Int.getWithIntegerDecimals(before: String, separator: Char, after: String): String = if (this == 0) { before } else { diff --git a/data/qr-scanning/src/main/java/com/tangem/data/qrscanning/repository/DefaultQrScanningEventsRepository.kt b/data/qr-scanning/src/main/java/com/tangem/data/qrscanning/repository/DefaultQrScanningEventsRepository.kt index 87729e8bc4..213044a170 100644 --- a/data/qr-scanning/src/main/java/com/tangem/data/qrscanning/repository/DefaultQrScanningEventsRepository.kt +++ b/data/qr-scanning/src/main/java/com/tangem/data/qrscanning/repository/DefaultQrScanningEventsRepository.kt @@ -1,6 +1,7 @@ package com.tangem.data.qrscanning.repository import com.tangem.blockchain.common.Blockchain +import com.tangem.core.ui.utils.parseBigDecimalOrNull import com.tangem.domain.qrscanning.models.QrResult import com.tangem.domain.qrscanning.models.SourceType import com.tangem.domain.qrscanning.repository.QrScanningEventsRepository @@ -44,7 +45,7 @@ internal class DefaultQrScanningEventsRepository : QrScanningEventsRepository { when (it.key) { Parameter.Amount -> { // According to BIP-0021, the value is specified in decimals. No conversion needed - result.amount = it.value.toBigDecimalOrNull() + result.amount = it.value.parseBigDecimalOrNull() } Parameter.Message, Parameter.Memo, @@ -69,7 +70,7 @@ internal class DefaultQrScanningEventsRepository : QrScanningEventsRepository { -> { // Extra convert parses scientific notation to decimal // This is necessary to be able comparing BigDecimal values - result.amount = it.value.toBigDecimalOrNull() + result.amount = it.value.parseBigDecimalOrNull() ?.toPlainString()?.toBigDecimalOrNull() ?.divide(BigDecimal.TEN.pow(cryptoCurrency.decimals)) } From a6c54fbaae7ab31056b3ea443e683d9328018ca2 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 17 Apr 2024 12:21:37 +0300 Subject: [PATCH 66/67] Updated on 2026-08-14 --- .../impl/presentation/ui/BriefNetworksList.kt | 44 ++++++++++++------- 1 file changed, 29 insertions(+), 15 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/ui/BriefNetworksList.kt b/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/ui/BriefNetworksList.kt index d5328878eb..e122327ff8 100644 --- a/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/ui/BriefNetworksList.kt +++ b/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/ui/BriefNetworksList.kt @@ -4,20 +4,20 @@ import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.CircleShape import androidx.compose.material.Icon import androidx.compose.material.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.key +import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip +import androidx.compose.ui.draw.drawWithContent import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.painterResource +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.TextUnit +import androidx.compose.ui.unit.TextUnitType import com.tangem.core.ui.res.TangemTheme import com.tangem.tap.features.tokens.impl.presentation.states.NetworkItemState import kotlinx.collections.immutable.ImmutableCollection @@ -41,10 +41,7 @@ internal fun BriefNetworksList( exit = fadeOut(), ) { Row(horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing4)) { - val iterator = networks.iterator() - var index = 0 - while (iterator.hasNext()) { - val network = iterator.next() + for ((index, network) in networks.withIndex()) { if (index < MAX_VISIBLE_BRIEF_ICONS) { key(network.name + network.protocolName) { BriefNetworkItem(model = network) @@ -59,7 +56,6 @@ internal fun BriefNetworksList( break } } - index++ } } } @@ -109,8 +105,16 @@ internal fun BriefNetworkItem(model: NetworkItemState, modifier: Modifier = Modi } } +@Suppress("MagicNumber") @Composable internal fun HasMoreItem(moreCount: Int) { + val count = if (moreCount > 99) 99 else moreCount + val themeTextStyle = TangemTheme.typography.overline.copy( + letterSpacing = TextUnit(value = 0f, type = TextUnitType.Sp), + ) + var textStyle by remember(themeTextStyle) { mutableStateOf(themeTextStyle) } + var readyToDraw by remember(themeTextStyle) { mutableStateOf(false) } + Box( modifier = Modifier .size(size = TangemTheme.dimens.size20) @@ -118,10 +122,20 @@ internal fun HasMoreItem(moreCount: Int) { .background(TangemTheme.colors.control.unchecked), ) { Text( - modifier = Modifier.align(Alignment.Center), - text = "+$moreCount", - style = TangemTheme.typography.overline, - color = TangemTheme.colors.text.tertiary, + modifier = Modifier + .padding(TangemTheme.dimens.spacing4) + .align(Alignment.Center) + .drawWithContent { if (readyToDraw) drawContent() }, + text = "+$count", + style = textStyle, + overflow = TextOverflow.Clip, + onTextLayout = { textLayoutResult -> + if (textLayoutResult.didOverflowHeight) { + textStyle = textStyle.copy(fontSize = textStyle.fontSize * 0.9) + } else { + readyToDraw = true + } + }, ) } } \ No newline at end of file From ec1b9093ece71431287cec5efbfcac6028a8c55c Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 17 Apr 2024 15:49:31 +0500 Subject: [PATCH 67/67] Updated on 2026-08-14 --- .../com/tangem/core/ui/utils/DecimalFormatterExt.kt | 13 ++++++++++++- .../DefaultQrScanningEventsRepositoryTest.kt | 10 +++++----- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/core/ui/src/main/java/com/tangem/core/ui/utils/DecimalFormatterExt.kt b/core/ui/src/main/java/com/tangem/core/ui/utils/DecimalFormatterExt.kt index b5a21b53f7..5006df1d7a 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/utils/DecimalFormatterExt.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/utils/DecimalFormatterExt.kt @@ -12,6 +12,7 @@ import java.util.Locale private const val TEXT_CHUNK_THOUSAND = 3 private const val POINT_SEPARATOR = '.' private const val COMMA_SEPARATOR = ',' +private const val SCIENTIFIC_NOTATION = 'e' const val DECIMAL_SEPARATOR_LIMIT = 1 @Composable @@ -162,7 +163,17 @@ fun BigDecimal.parseBigDecimal(decimals: Int, roundingMode: RoundingMode = Round fun String.parseBigDecimalOrNull() = runCatching { // Filtering value containing more than one either grouping or decimal separator. // We assume there will be only decimal separator, otherwise parsing will fail. - if (count { !it.isDigit() } > DECIMAL_SEPARATOR_LIMIT) return null + + // Step 1. Exclude formatted (100,000.0) except scientific notation (100.000e10) + val excludeFormatted = this.count { + !it.isDigit() && !it.equals(SCIENTIFIC_NOTATION, ignoreCase = true) + } > DECIMAL_SEPARATOR_LIMIT + + // Step 2. Exclude wrong scientific notation (100e100e100) + val excludeWrongScientific = this.count { + it.equals(SCIENTIFIC_NOTATION, ignoreCase = true) + } > DECIMAL_SEPARATOR_LIMIT + if (excludeFormatted || excludeWrongScientific) return null // An attempt to parse value with POINT decimal separator val parsed = this.toBigDecimalOrNull() diff --git a/data/qr-scanning/src/test/java/com/tangem/data/qrscanning/DefaultQrScanningEventsRepositoryTest.kt b/data/qr-scanning/src/test/java/com/tangem/data/qrscanning/DefaultQrScanningEventsRepositoryTest.kt index 13d49ce302..3b3cd6df38 100644 --- a/data/qr-scanning/src/test/java/com/tangem/data/qrscanning/DefaultQrScanningEventsRepositoryTest.kt +++ b/data/qr-scanning/src/test/java/com/tangem/data/qrscanning/DefaultQrScanningEventsRepositoryTest.kt @@ -152,11 +152,6 @@ internal class DefaultQrScanningEventsRepositoryTest { QrResult(address = address2), cryptoCurrency, ) - positiveCase( - "$garbage$schema2:$address2$function?$addressParam=$addressParamValue", - QrResult(address = address2), - cryptoCurrency, - ) positiveCase( "$garbage$schema2:$address2?$someParam=$someParamValue&$valueParam=$someAmountParamValue", QrResult(address = address2), @@ -177,6 +172,11 @@ internal class DefaultQrScanningEventsRepositoryTest { QrResult(address = address2, amount = BigDecimal("0.000000023")), cryptoCurrency, ) + negativeCase( + "$garbage$schema2:$address2$function?$addressParam=$addressParamValue", + QrResult(address = address2), + cryptoCurrency, + ) } @Test