From b76f6d4ae72915be4073646400a5406f26e9b796 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 28 Feb 2023 14:44:37 +0300 Subject: [PATCH 01/17] Updated on 2026-08-14 --- .../com/tangem/tap/LockUserWalletsTimer.kt | 6 +- .../main/java/com/tangem/tap/MainActivity.kt | 4 +- .../userWalletList/UserWalletsListManager.kt | 80 ++++++++------ .../UserWalletsListManagerExtensions.kt | 51 +++++++++ .../di/UserWalletsListManagerProvider.kt | 7 ++ .../BiometricUserWalletsListManager.kt | 14 +-- .../RuntimeUserWalletsListManager.kt | 103 ++++++++++++++++++ .../details/redux/DetailsMiddleware.kt | 4 +- .../twins/redux/TwinCardsMiddleware.kt | 3 +- .../saveWallet/redux/SaveWalletMiddleware.kt | 2 +- .../features/tokens/redux/TokensMiddleware.kt | 2 +- .../tap/features/wallet/redux/WalletState.kt | 2 +- .../redux/middlewares/WalletMiddleware.kt | 2 +- .../redux/reducers/MultiWalletReducer.kt | 2 +- .../tap/features/wallet/ui/WalletFragment.kt | 7 +- .../redux/WalletSelectorMiddleware.kt | 9 +- .../ui/WalletSelectorViewModel.kt | 1 + .../welcome/redux/WelcomeMiddleware.kt | 3 +- 18 files changed, 240 insertions(+), 62 deletions(-) create mode 100644 app/src/main/java/com/tangem/tap/domain/userWalletList/UserWalletsListManagerExtensions.kt create mode 100644 app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/RuntimeUserWalletsListManager.kt diff --git a/app/src/main/java/com/tangem/tap/LockUserWalletsTimer.kt b/app/src/main/java/com/tangem/tap/LockUserWalletsTimer.kt index 426f49ca25..9ad0fc6fa9 100644 --- a/app/src/main/java/com/tangem/tap/LockUserWalletsTimer.kt +++ b/app/src/main/java/com/tangem/tap/LockUserWalletsTimer.kt @@ -6,6 +6,7 @@ import androidx.lifecycle.lifecycleScope import com.tangem.tap.common.extensions.dispatchOnMain import com.tangem.tap.common.redux.navigation.AppScreen import com.tangem.tap.common.redux.navigation.NavigationAction +import com.tangem.tap.domain.userWalletList.asLockable import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import kotlinx.coroutines.delay @@ -95,8 +96,9 @@ internal class LockUserWalletsTimer( val startTime = System.currentTimeMillis() delay(duration) if (isActive) { - val userWalletsListManager = userWalletsListManagerSafe ?: return@launch - if (userWalletsListManager.hasSavedUserWallets) { + val userWalletsListManager = userWalletsListManagerSafe?.asLockable() + ?: return@launch + if (userWalletsListManager.hasUserWallets) { val currentTime = System.currentTimeMillis() Timber.d( """ diff --git a/app/src/main/java/com/tangem/tap/MainActivity.kt b/app/src/main/java/com/tangem/tap/MainActivity.kt index 6b5d0bd175..c3507df132 100644 --- a/app/src/main/java/com/tangem/tap/MainActivity.kt +++ b/app/src/main/java/com/tangem/tap/MainActivity.kt @@ -122,7 +122,7 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac override fun onNewIntent(intent: Intent?) { super.onNewIntent(intent) - intentHandler.handleIntent(intent, userWalletsListManager.hasSavedUserWallets) + intentHandler.handleIntent(intent, userWalletsListManager.hasUserWallets) } override fun onStart() { @@ -204,7 +204,7 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac } private fun navigateToInitialScreen(intent: Intent?) { - if (userWalletsListManager.hasSavedUserWallets) { + if (userWalletsListManager.hasUserWallets) { store.dispatchOnMain(NavigationAction.NavigateTo(AppScreen.Welcome)) store.dispatchOnMain(WelcomeAction.HandleIntentIfNeeded(intent)) } else { diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/UserWalletsListManager.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/UserWalletsListManager.kt index 91d3b8dd91..ea9b4c6bb9 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/UserWalletsListManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/UserWalletsListManager.kt @@ -22,47 +22,19 @@ interface UserWalletsListManager { val selectedUserWalletSync: UserWallet? /** - * Indicates that all [UserWallet]s is unlocked - * - * @see [isLockedSync] - * @see [UserWallet.isLocked] + * Indicates that the [UserWalletsListManager] contains at least one saved [UserWallet] * */ - val isLocked: Flow - - /** - * Indicates that all [UserWallet]s is unlocked - * - * Sync version - * - * @see [isLocked] - * @see [UserWallet.isLocked] - * */ - val isLockedSync: Boolean - - val hasSavedUserWallets: Boolean - - /** - * Receive saved [UserWallet]s, populate [userWallets] flow with it and set [isLocked] as false. - * Require biometric user authorization - * - * @return [CompletionResult] of operation, with selected [UserWallet] - * or null if there is no selected [UserWallet] - * */ - suspend fun unlockWithBiometry(): CompletionResult - - /** - * Remove [UserWallet]s from [userWallets] and set [isLocked] as true - * */ - fun lock() + val hasUserWallets: Boolean /** * Set [UserWallet] with provided [UserWalletId] as selected * * @param userWalletId [UserWalletId] of [UserWallet] which must be selected * - * @return [CompletionResult] of operation with selected [UserWallet] + * @return [CompletionResult.Success] with selected [UserWallet] + * or [CompletionResult.Failure] with [NoSuchElementException] if [UserWallet] with [userWalletId] not found * */ - suspend fun selectWallet(userWalletId: UserWalletId): CompletionResult + suspend fun select(userWalletId: UserWalletId): CompletionResult /** * Save provided user wallet and set it as selected @@ -80,13 +52,16 @@ interface UserWalletsListManager { * Can terminate with [NoSuchElementException] if unable to find [UserWallet] with provided [UserWalletId] * @param userWalletId update [UserWallet] with that [UserWalletId] * @param update lambda that receives stored [UserWallet] and returns updated [UserWallet] - * @return [CompletionResult] of operation with updated [UserWallet] + * @return [CompletionResult.Success] with updated [UserWallet] + * or [CompletionResult.Failure] with [NoSuchElementException] if [UserWallet] with [userWalletId] not found * */ suspend fun update(userWalletId: UserWalletId, update: (UserWallet) -> UserWallet): CompletionResult /** * Delete saved [UserWallet]s with provided [UserWalletId]s * + * Sets [isLocked] as true if [userWallets] is empty or if all [userWallets] are locked + * * @param userWalletIds [UserWalletId]s of [UserWallet]s which must be deleted * * @return [CompletionResult] of operation @@ -102,12 +77,45 @@ interface UserWalletsListManager { /** * Get [UserWallet] with provided [UserWalletId] - * May terminate with [NoSuchElementException] if [UserWallet] is not found * - * @return [CompletionResult] of operation with found [UserWallet] + * @return [CompletionResult.Success] with found [UserWallet] + * or [CompletionResult.Failure] with [NoSuchElementException] if [UserWallet] with [userWalletId] not found * */ suspend fun get(userWalletId: UserWalletId): CompletionResult + interface Lockable : UserWalletsListManager { + /** + * Indicates that all [UserWallet]s is locked + * + * @see [isLockedSync] + * @see [UserWallet.isLocked] + * */ + val isLocked: Flow + + /** + * Indicates that all [UserWallet]s is locked + * + * Sync version + * + * @see [isLocked] + * @see [UserWallet.isLocked] + * */ + val isLockedSync: Boolean + + /** + * Receive saved [UserWallet]s, populate [userWallets] flow with it and set [isLocked] as false. + * + * @return [CompletionResult] of operation, with selected [UserWallet] + * or null if there is no selected [UserWallet] + * */ + suspend fun unlock(): CompletionResult + + /** + * Remove [UserWallet]s from [userWallets] and set [isLocked] as true + * */ + fun lock() + } + // For provider companion object } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/UserWalletsListManagerExtensions.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/UserWalletsListManagerExtensions.kt new file mode 100644 index 0000000000..47f8b18a7c --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/UserWalletsListManagerExtensions.kt @@ -0,0 +1,51 @@ +package com.tangem.tap.domain.userWalletList + +import com.tangem.common.CompletionResult +import com.tangem.tap.domain.model.UserWallet +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flowOf + +/** + * Indicates that the [UserWalletsListManager] is locked + * + * @return If [UserWalletsListManager] not implements [UserWalletsListManager.Lockable] returns [Flow] which + * produces only one false value + * */ +val UserWalletsListManager.isLocked: Flow + get() = asLockable()?.isLocked ?: flowOf(false) + +/** + * Indicates that the [UserWalletsListManager] is locked + * + * @return If [UserWalletsListManager] not implements [UserWalletsListManager.Lockable] returns false + * */ +val UserWalletsListManager.isLockedSync: Boolean + get() = asLockable()?.isLockedSync ?: false + +/** + * Call [UserWalletsListManager.Lockable.unlock] if [UserWalletsListManager] implements [UserWalletsListManager.Lockable] + * + * @return If [UserWalletsListManager] not implements [UserWalletsListManager.Lockable] + * returns [CompletionResult.Success] with [UserWalletsListManager.selectedUserWalletSync] + * */ +suspend fun UserWalletsListManager.unlockIfLockable(): CompletionResult { + return asLockable()?.unlock() ?: CompletionResult.Success(selectedUserWalletSync) +} + +/** + * Call [UserWalletsListManager.Lockable.lock] if [UserWalletsListManager] implements [UserWalletsListManager.Lockable] + * or do nothing otherwise + * */ +fun UserWalletsListManager.lockIfLockable() { + asLockable()?.lock() +} + +/** + * Safe cast [UserWalletsListManager] to [UserWalletsListManager.Lockable] + * + * @return If [UserWalletsListManager] not implements [UserWalletsListManager.Lockable] then returns null or + * [UserWalletsListManager.Lockable] otherwise + * */ +fun UserWalletsListManager.asLockable(): UserWalletsListManager.Lockable? { + return this as? UserWalletsListManager.Lockable +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/di/UserWalletsListManagerProvider.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/di/UserWalletsListManagerProvider.kt index 3773da7127..163c0cdc60 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/di/UserWalletsListManagerProvider.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/di/UserWalletsListManagerProvider.kt @@ -10,6 +10,7 @@ import com.tangem.tangem_sdk_new.storage.createEncryptedSharedPreferences import com.tangem.tap.domain.TangemSdkManager import com.tangem.tap.domain.userWalletList.UserWalletsListManager import com.tangem.tap.domain.userWalletList.implementation.BiometricUserWalletsListManager +import com.tangem.tap.domain.userWalletList.implementation.RuntimeUserWalletsListManager import com.tangem.tap.domain.userWalletList.repository.implementation.BiometricUserWalletsKeysRepository import com.tangem.tap.domain.userWalletList.repository.implementation.DefaultSelectedUserWalletRepository import com.tangem.tap.domain.userWalletList.repository.implementation.DefaultUserWalletsPublicInformationRepository @@ -68,4 +69,10 @@ fun UserWalletsListManager.Companion.provideBiometricImplementation( sensitiveInformationRepository = sensitiveInformationRepository, selectedUserWalletRepository = selectedUserWalletRepository, ) +} + +// TODO: Will be used in further MR's +@Suppress("unused") +fun UserWalletsListManager.Companion.provideRuntimeImplementation(): UserWalletsListManager { + return RuntimeUserWalletsListManager() } \ No newline at end of file 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 5c5be22e1f..060464fd2f 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 @@ -23,7 +23,7 @@ internal class BiometricUserWalletsListManager( private val publicInformationRepository: UserWalletsPublicInformationRepository, private val sensitiveInformationRepository: UserWalletsSensitiveInformationRepository, private val selectedUserWalletRepository: SelectedUserWalletRepository, -) : UserWalletsListManager { +) : UserWalletsListManager.Lockable { private val state = MutableStateFlow(State()) override val userWallets: Flow> @@ -50,10 +50,10 @@ internal class BiometricUserWalletsListManager( override val isLockedSync: Boolean get() = state.value.isLocked - override val hasSavedUserWallets: Boolean + override val hasUserWallets: Boolean get() = keysRepository.hasSavedEncryptionKeys() - override suspend fun unlockWithBiometry(): CompletionResult { + override suspend fun unlock(): CompletionResult { return unlockWithBiometryInternal() .map { selectedUserWalletSync } } @@ -62,20 +62,20 @@ internal class BiometricUserWalletsListManager( state.update { State() } } - override suspend fun selectWallet(userWalletId: UserWalletId): CompletionResult = catching { + override suspend fun select(userWalletId: UserWalletId): CompletionResult = catching { if (state.value.selectedUserWalletId == userWalletId) { return@catching findSelectedUserWallet()!! } selectedUserWalletRepository.set(userWalletId) - state.update { prevState -> + val newState = state.updateAndGet { prevState -> prevState.copy( selectedUserWalletId = userWalletId, ) } - findSelectedUserWallet()!! + newState.userWallets.first { it.walletId == userWalletId } } override suspend fun save(userWallet: UserWallet, canOverride: Boolean): CompletionResult { @@ -289,7 +289,7 @@ internal class BiometricUserWalletsListManager( } private fun findSelectedUserWallet(userWallets: List = state.value.userWallets): UserWallet? { - return userWallets.find { + return userWallets.firstOrNull { it.walletId == state.value.selectedUserWalletId } } 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 new file mode 100644 index 0000000000..01a31ebff4 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/RuntimeUserWalletsListManager.kt @@ -0,0 +1,103 @@ +package com.tangem.tap.domain.userWalletList.implementation + +import com.tangem.common.CompletionResult +import com.tangem.common.catching +import com.tangem.domain.common.util.UserWalletId +import com.tangem.tap.domain.model.UserWallet +import com.tangem.tap.domain.userWalletList.UserWalletListError +import com.tangem.tap.domain.userWalletList.UserWalletsListManager +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.filterNotNull +import kotlinx.coroutines.flow.mapLatest +import kotlinx.coroutines.flow.update +import kotlinx.coroutines.flow.updateAndGet + +@OptIn(ExperimentalCoroutinesApi::class) +internal class RuntimeUserWalletsListManager : UserWalletsListManager { + private val state = MutableStateFlow(State()) + + override val userWallets: Flow> + get() = state + .mapLatest { listOfNotNull(it.userWallet) } + .distinctUntilChanged() + + override val selectedUserWallet: Flow + get() = state + .mapLatest { it.userWallet } + .filterNotNull() + .distinctUntilChanged() + + override val selectedUserWalletSync: UserWallet? + get() = state.value.userWallet + + override val hasUserWallets: Boolean + get() = state.value.userWallet != null + + override suspend fun select(userWalletId: UserWalletId): CompletionResult = catching { + state.value.userWallet + ?.takeIf { it.walletId == userWalletId } + ?: walletNotFound() + } + + override suspend fun save(userWallet: UserWallet, canOverride: Boolean): CompletionResult { + return if (canOverride) { + saveInternal(userWallet) + } else { + val isWalletSaved = state.value.userWallet?.walletId == userWallet.walletId + + if (isWalletSaved) { + CompletionResult.Failure(UserWalletListError.WalletAlreadySaved) + } else { + saveInternal(userWallet) + } + } + } + + override suspend fun update( + userWalletId: UserWalletId, + update: (UserWallet) -> UserWallet, + ): CompletionResult = catching { + val wallet = state.value.userWallet + ?.takeIf { it.walletId == userWalletId } + ?: walletNotFound() + + state.updateAndGet { prevState -> + prevState.copy( + userWallet = update(wallet), + ) + }.userWallet!! + } + + override suspend fun delete(userWalletIds: List): CompletionResult = clear() + + override suspend fun clear(): CompletionResult = catching { + state.update { prevState -> + prevState.copy( + userWallet = null, + ) + } + } + + override suspend fun get(userWalletId: UserWalletId): CompletionResult = catching { + state.value.userWallet ?: walletNotFound() + } + + private fun saveInternal(userWallet: UserWallet): CompletionResult = catching { + state.update { prevState -> + prevState.copy( + userWallet = userWallet, + ) + } + } + + private fun walletNotFound(): Nothing { + throw NoSuchElementException("User wallet not found") + } + + private data class State( + val userWallet: UserWallet? = null, + ) +} \ No newline at end of file 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 983be0f335..d5032c7ce7 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 @@ -20,6 +20,8 @@ import com.tangem.tap.common.redux.navigation.AppScreen import com.tangem.tap.common.redux.navigation.NavigationAction import com.tangem.tap.domain.model.builders.UserWalletBuilder import com.tangem.tap.domain.model.builders.UserWalletIdBuilder +import com.tangem.tap.domain.userWalletList.isLockedSync +import com.tangem.tap.domain.userWalletList.lockIfLockable import com.tangem.tap.features.demo.DemoHelper import com.tangem.tap.features.onboarding.products.twins.redux.CreateTwinWalletMode import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsAction @@ -132,7 +134,7 @@ class DetailsMiddleware { store.onUserWalletSelected(selectedUserWallet) } } else { - userWalletsListManager.lock() + userWalletsListManager.lockIfLockable() store.dispatchOnMain(NavigationAction.PopBackTo(AppScreen.Home)) } } diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsMiddleware.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsMiddleware.kt index 1f77d2ae6f..ff7ff2399e 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsMiddleware.kt @@ -26,6 +26,7 @@ import com.tangem.tap.domain.TapError import com.tangem.tap.domain.extensions.makePrimaryWalletManager import com.tangem.tap.domain.model.builders.UserWalletIdBuilder import com.tangem.tap.domain.twins.TwinCardsManager +import com.tangem.tap.domain.userWalletList.isLockedSync import com.tangem.tap.features.home.RUSSIA_COUNTRY_CODE import com.tangem.tap.features.onboarding.OnboardingDialog import com.tangem.tap.features.onboarding.OnboardingHelper @@ -335,7 +336,7 @@ private fun handle(action: Action, dispatch: DispatchFunction) { } private fun getPopBackScreen(): AppScreen { - return if (userWalletsListManager.hasSavedUserWallets) { + return if (userWalletsListManager.hasUserWallets) { if (userWalletsListManager.isLockedSync) { AppScreen.Welcome } else { 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 2dfa79cb24..5eaa50d8bc 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 @@ -90,7 +90,7 @@ internal class SaveWalletMiddleware { .backupCardsIds(state.backupInfo?.backupCardsIds) .build() ?: return@launch - val isFirstSavedWallet = !userWalletsListManager.hasSavedUserWallets + val isFirstSavedWallet = !userWalletsListManager.hasUserWallets saveAccessCodeIfNeeded(state.backupInfo?.accessCode, userWallet.cardsInWallet) .flatMap { userWalletsListManager.save(userWallet, canOverride = true) } diff --git a/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensMiddleware.kt b/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensMiddleware.kt index 27aa168f02..4e71d8a002 100644 --- a/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensMiddleware.kt @@ -341,7 +341,7 @@ class TokensMiddleware { private suspend fun removeCurrenciesIfNeeded(currencies: List) { when { currencies.isEmpty() -> Unit - userWalletsListManager.hasSavedUserWallets -> { + userWalletsListManager.hasUserWallets -> { walletCurrenciesManager.removeCurrencies( userWallet = userWalletsListManager.selectedUserWalletSync!!, currenciesToRemove = currencies, diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletState.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletState.kt index 73c49d69ef..973966bc4f 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletState.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletState.kt @@ -103,7 +103,7 @@ data class WalletState( primaryWalletData?.currencyData?.status != BalanceStatus.UnknownBlockchain val hasSavedWallets: Boolean - get() = userWalletsListManager.hasSavedUserWallets + get() = userWalletsListManager.hasUserWallets fun getWalletManager(currency: Currency?): WalletManager? { if (currency?.blockchain == null) return null diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt index 41a28fa9c9..b1d86b23f1 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt @@ -377,7 +377,7 @@ class WalletMiddleware { private fun changeWallet() { when { - userWalletsListManager.hasSavedUserWallets -> { + userWalletsListManager.hasUserWallets -> { Analytics.send(MainScreen.ButtonMyWallets()) store.dispatchOnMain(NavigationAction.NavigateTo(AppScreen.WalletSelector)) } diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/MultiWalletReducer.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/MultiWalletReducer.kt index 1acc7600fd..e415a746a2 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/MultiWalletReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/MultiWalletReducer.kt @@ -114,7 +114,7 @@ class MultiWalletReducer { val currency = Currency.fromBlockchainNetwork(action.blockchain, action.token) val walletManager = state.getWalletManager(currency) if (walletManager == null) { - val screen = if (userWalletsListManager.hasSavedUserWallets) { + val screen = if (userWalletsListManager.hasUserWallets) { AppScreen.Welcome } else { AppScreen.Home diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletFragment.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletFragment.kt index b170637f3b..e35f31191d 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletFragment.kt @@ -32,6 +32,7 @@ import com.tangem.tap.common.redux.navigation.NavigationAction import com.tangem.tap.domain.configurable.warningMessage.WarningMessage import com.tangem.tap.domain.statePrinter.printScanResponseState import com.tangem.tap.domain.statePrinter.printWalletState +import com.tangem.tap.domain.userWalletList.lockIfLockable import com.tangem.tap.features.details.redux.DetailsAction import com.tangem.tap.features.wallet.redux.ErrorType import com.tangem.tap.features.wallet.redux.ProgressState @@ -81,13 +82,13 @@ class WalletFragment : Fragment(R.layout.fragment_wallet), StoreSubscriber { - unlockWalletsWithBiometry() + unlockWallets() } is WalletSelectorAction.AddWallet -> { addWallet() @@ -115,11 +116,11 @@ internal class WalletSelectorMiddleware { } } - private fun unlockWalletsWithBiometry() { + private fun unlockWallets() { Analytics.send(MyWallets.Button.UnlockWithBiometrics()) scope.launch { - userWalletsListManager.unlockWithBiometry() + userWalletsListManager.unlockIfLockable() .doOnFailure { error -> Timber.e(error, "Unable to unlock all user wallets") store.dispatchOnMain(WalletSelectorAction.UnlockWithBiometry.Error(error)) @@ -195,7 +196,7 @@ internal class WalletSelectorMiddleware { if (userWallet.isLocked) { unlockUserWalletWithScannedCard(userWallet) } else { - userWalletsListManager.selectWallet(userWalletId) + userWalletsListManager.select(userWalletId) } } .doOnFailure { error -> diff --git a/app/src/main/java/com/tangem/tap/features/walletSelector/ui/WalletSelectorViewModel.kt b/app/src/main/java/com/tangem/tap/features/walletSelector/ui/WalletSelectorViewModel.kt index 41e5ca44f0..b048098525 100644 --- a/app/src/main/java/com/tangem/tap/features/walletSelector/ui/WalletSelectorViewModel.kt +++ b/app/src/main/java/com/tangem/tap/features/walletSelector/ui/WalletSelectorViewModel.kt @@ -8,6 +8,7 @@ import com.tangem.core.analytics.Analytics import com.tangem.domain.common.util.UserWalletId import com.tangem.tap.common.analytics.events.MyWallets import com.tangem.tap.common.extensions.dispatchOnMain +import com.tangem.tap.domain.userWalletList.isLocked import com.tangem.tap.features.details.ui.cardsettings.TextReference import com.tangem.tap.features.walletSelector.redux.WalletSelectorAction import com.tangem.tap.features.walletSelector.redux.WalletSelectorState 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 94d5b55b3c..c8cde60e79 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 @@ -17,6 +17,7 @@ import com.tangem.tap.common.redux.navigation.AppScreen import com.tangem.tap.common.redux.navigation.NavigationAction import com.tangem.tap.domain.model.builders.UserWalletBuilder import com.tangem.tap.domain.scanCard.ScanCardProcessor +import com.tangem.tap.domain.userWalletList.unlockIfLockable import com.tangem.tap.features.onboarding.products.wallet.saltPay.message.SaltPayActivationError import com.tangem.tap.intentHandler import com.tangem.tap.preferencesStorage @@ -66,7 +67,7 @@ internal class WelcomeMiddleware { private fun proceedWithBiometrics(state: WelcomeState) { scope.launch { - userWalletsListManager.unlockWithBiometry() + userWalletsListManager.unlockIfLockable() .doOnFailure { error -> store.dispatchOnMain(WelcomeAction.ProceedWithBiometrics.Error(error)) } From 9ddbc82073a327f433f76b425b6cd2a6c3761e5f Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 1 Mar 2023 17:41:33 +0300 Subject: [PATCH 02/17] Updated on 2026-08-14 --- .../domain/common/extensions/CardSdk.kt | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/domain/src/main/java/com/tangem/domain/common/extensions/CardSdk.kt b/domain/src/main/java/com/tangem/domain/common/extensions/CardSdk.kt index bd7a3d7643..c7d205bbe0 100644 --- a/domain/src/main/java/com/tangem/domain/common/extensions/CardSdk.kt +++ b/domain/src/main/java/com/tangem/domain/common/extensions/CardSdk.kt @@ -13,15 +13,12 @@ val FirmwareVersion.Companion.SolanaTokensAvailable get() = FirmwareVersion(4, 52) fun CardDTO.supportedBlockchains(): List { - val supportedBlockchains = when { - firmwareVersion < FirmwareVersion.MultiWalletAvailable -> { - Blockchain.fromCurve(EllipticCurve.Secp256k1) - } - - else -> { - (Blockchain.fromCurve(EllipticCurve.Secp256k1) + Blockchain.fromCurve(EllipticCurve.Ed25519)).distinct() - } + val supportedBlockchains = if (firmwareVersion < FirmwareVersion.MultiWalletAvailable) { + Blockchain.fromCurve(EllipticCurve.Secp256k1) + } else { + wallets.flatMap { Blockchain.fromCurve(it.curve) }.distinct() } + return supportedBlockchains .filter { isTestCard == it.isTestnet() } .filter { it.isSupportedInApp() } @@ -38,12 +35,8 @@ fun CardDTO.supportedTokens(): List { } } } - val filtered = tokensSupportedByCard.filter { isTestCard == it.isTestnet() } - return filtered -} -fun CardDTO.canHandleBlockchain(blockchain: Blockchain): Boolean { - return this.supportedBlockchains().contains(blockchain) + return tokensSupportedByCard.filter { isTestCard == it.isTestnet() } } fun CardDTO.canHandleToken(blockchain: Blockchain): Boolean { From b44da0a4c0b4ac5881f2ebcc13edaa7e8eac4ae3 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 1 Mar 2023 19:10:57 +0300 Subject: [PATCH 03/17] Updated on 2026-08-14 --- buildSrc/src/main/java/Versions.kt | 2 +- .../java/com/tangem/datasource/config/ConfigManagerImpl.kt | 2 +- .../java/com/tangem/datasource/config/models/SaltPayConfig.kt | 4 +--- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/buildSrc/src/main/java/Versions.kt b/buildSrc/src/main/java/Versions.kt index 32ba2da6ab..96bf06edbb 100644 --- a/buildSrc/src/main/java/Versions.kt +++ b/buildSrc/src/main/java/Versions.kt @@ -61,7 +61,7 @@ object Versions { // endregion Other libraries // region Tangem - const val tangemBlockchainSdk = "develop-168" + const val tangemBlockchainSdk = "develop-172" // const val tangemBlockchainSdk = "0.0.1" // Keep it! - used for local builds const val tangemCardSdk = "develop-191" 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 e438e85955..d0c6d148c2 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 @@ -99,7 +99,7 @@ internal class ConfigManagerImpl @Inject constructor() : ConfigManager { ), infuraProjectId = configValues.infuraProjectId, tronGridApiKey = configValues.tronGridApiKey, - saltPayAuthToken = configValues.saltPay.credentials.token, + saltPayAuthToken = configValues.saltPay.credentials.basicAuthToken, nowNodeCredentials = NowNodeCredentials(configValues.nowNodesApiKey), getBlockCredentials = GetBlockCredentials(configValues.getBlockApiKey), ), diff --git a/core/datasource/src/main/java/com/tangem/datasource/config/models/SaltPayConfig.kt b/core/datasource/src/main/java/com/tangem/datasource/config/models/SaltPayConfig.kt index f8148bcf9e..8e38b0f971 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/config/models/SaltPayConfig.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/config/models/SaltPayConfig.kt @@ -1,7 +1,5 @@ package com.tangem.datasource.config.models -import org.spongycastle.util.encoders.Base64.toBase64String - /** [REDACTED_AUTHOR] */ @@ -34,5 +32,5 @@ data class Credentials( val user: String, val password: String, ) { - val token: String by lazy { "Basic ${toBase64String("$user:$password".toByteArray())}" } + val basicAuthToken: String by lazy { okhttp3.Credentials.basic(user, password) } } \ No newline at end of file From 52b44e87cf6eb51290303c511b572c2b6cbad43f Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 1 Mar 2023 19:40:32 +0300 Subject: [PATCH 04/17] Updated on 2026-08-14 --- .../DefaultWalletCurrenciesManager.kt | 9 ++++--- .../DefaultWalletAmountsRepository.kt | 9 +++++-- .../DefaultWalletManagersRepository.kt | 27 ++++++++++++++++--- .../utils/WalletStoreOperations.kt | 7 ++--- 4 files changed, 39 insertions(+), 13 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/domain/walletCurrencies/implementation/DefaultWalletCurrenciesManager.kt b/app/src/main/java/com/tangem/tap/domain/walletCurrencies/implementation/DefaultWalletCurrenciesManager.kt index b9292c096c..5fe6dd8e22 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletCurrencies/implementation/DefaultWalletCurrenciesManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletCurrencies/implementation/DefaultWalletCurrenciesManager.kt @@ -1,6 +1,5 @@ package com.tangem.tap.domain.walletCurrencies.implementation -import com.tangem.blockchain.common.Blockchain import com.tangem.blockchain.common.DerivationStyle import com.tangem.common.CompletionResult import com.tangem.common.flatMap @@ -69,7 +68,7 @@ internal class DefaultWalletCurrenciesManager( .flatMap { updateWalletStoresAmounts( userWallet = userWallet, - updatedBlockchains = currenciesToAdd.map { it.blockchain }.distinct(), + updatedCurrencies = currenciesToAddWithMissingBlockchains, ) } } @@ -220,11 +219,13 @@ internal class DefaultWalletCurrenciesManager( private suspend fun updateWalletStoresAmounts( userWallet: UserWallet, - updatedBlockchains: List, + updatedCurrencies: List, ): CompletionResult { + val updatedBlockchains = updatedCurrencies + .filterIsInstance() val updatedWalletStores = walletStoresRepository.get(userWallet.walletId) .firstOrNull() - ?.filter { it.blockchain in updatedBlockchains } + ?.filter { it.blockchainWalletData.currency in updatedBlockchains } ?: return CompletionResult.Success(Unit) return walletAmountsRepository.updateAmountsForWalletStores( diff --git a/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/DefaultWalletAmountsRepository.kt b/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/DefaultWalletAmountsRepository.kt index 1080f7529f..dff146b175 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/DefaultWalletAmountsRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/DefaultWalletAmountsRepository.kt @@ -142,8 +142,8 @@ internal class DefaultWalletAmountsRepository( Timber.e( error, """ - Unable to fetch fiat rates - |- Coins ids: $coinsIds + Unable to fetch fiat rates + |- Coins ids: $coinsIds """.trimIndent(), ) @@ -278,6 +278,7 @@ internal class DefaultWalletAmountsRepository( Unable to fetch amounts |- User wallet id: ${walletStore.userWalletId} |- Blockchain: ${walletStore.blockchain} + |- Derivation path: ${walletStore.derivationPath?.rawPath} """.trimIndent(), ) @@ -310,6 +311,7 @@ internal class DefaultWalletAmountsRepository( Fetched amounts |- User wallet id: ${walletStore.userWalletId} |- Blockchain: ${walletStore.blockchain} + |- Derivation path: ${walletStore.derivationPath?.rawPath} """.trimIndent(), ) @@ -337,6 +339,7 @@ internal class DefaultWalletAmountsRepository( Missed derivation |- User wallet id: ${walletStore.userWalletId} |- Blockchain: ${walletStore.blockchain} + |- Derivation path: ${walletStore.derivationPath?.rawPath} """.trimIndent(), ) @@ -360,6 +363,7 @@ internal class DefaultWalletAmountsRepository( Wallet manager is null |- User wallet id: ${walletStore.userWalletId} |- Blockchain: ${walletStore.blockchain} + |- Derivation path: ${walletStore.derivationPath?.rawPath} """.trimIndent(), ) @@ -405,6 +409,7 @@ internal class DefaultWalletAmountsRepository( Fetched wallet rent |- User wallet id: ${walletStore.userWalletId} |- Blockchain: ${walletStore.blockchain} + |- Derivation path: ${walletStore.derivationPath?.rawPath} |- Rent: $rent """.trimIndent(), ) diff --git a/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/DefaultWalletManagersRepository.kt b/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/DefaultWalletManagersRepository.kt index d9579349d6..6c1442d65d 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/DefaultWalletManagersRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/DefaultWalletManagersRepository.kt @@ -51,6 +51,7 @@ internal class DefaultWalletManagersRepository( val foundWalletManager = findWalletManager( userWalletId = userWallet.walletId, blockchain = blockchainNetwork?.blockchain, + derivationPath = blockchainNetwork?.derivationPath, ) foundWalletManager?.updateTokens( @@ -85,7 +86,13 @@ internal class DefaultWalletManagersRepository( return when { blockchain == Blockchain.Unknown || blockchain == null -> { val error = WalletStoresError.UnknownBlockchain() - Timber.e(error) + Timber.e( + error, + """ + Unknown blockchain while creating wallet manager + |- User wallet ID: ${userWallet.walletId} + """.trimIndent(), + ) CompletionResult.Failure(error) } walletManager != null -> { @@ -97,7 +104,15 @@ internal class DefaultWalletManagersRepository( } else -> { val error = WalletStoresError.WalletManagerNotCreated(blockchain) - Timber.e(error) + Timber.e( + error, + """ + Unable to create wallet manager + |- User wallet ID: ${userWallet.walletId} + |- Blockchain: $blockchain + |- Derivation path: ${blockchainNetwork?.derivationPath} + """.trimIndent(), + ) CompletionResult.Failure(error) } } @@ -178,15 +193,19 @@ internal class DefaultWalletManagersRepository( private suspend fun findWalletManager( userWalletId: UserWalletId, blockchain: Blockchain?, + derivationPath: String?, ): WalletManager? { return walletManagersStorage.getAll() .firstOrNull() ?.get(userWalletId) ?.let { userWalletManagers -> - if (blockchain == null) { + if (blockchain == null || derivationPath == null) { userWalletManagers.firstOrNull() } else { - userWalletManagers.firstOrNull { it.wallet.blockchain == blockchain } + userWalletManagers.firstOrNull { + it.wallet.blockchain == blockchain && + it.wallet.publicKey.derivationPath?.rawPath == derivationPath + } } } } diff --git a/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/utils/WalletStoreOperations.kt b/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/utils/WalletStoreOperations.kt index d052401b0c..efcd36e402 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/utils/WalletStoreOperations.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/utils/WalletStoreOperations.kt @@ -121,9 +121,10 @@ private inline fun List.replaceWalletStores( if (currentWalletStore != updatedWalletStore) { Timber.d( """ - Update wallet store in storage - |- User wallet ID: ${updatedWalletStore.userWalletId} - |- Blockchain: ${updatedWalletStore.blockchain} + Update wallet store in storage + |- User wallet ID: ${updatedWalletStore.userWalletId} + |- Blockchain: ${updatedWalletStore.blockchain} + |- Derivation path: ${updatedWalletStore.derivationPath?.rawPath} """.trimIndent(), ) From c540a570b531b1a2c23adaf8c55ce66957c83147 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 1 Mar 2023 20:17:35 +0300 Subject: [PATCH 05/17] Updated on 2026-08-14 --- app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt | 6 ++++-- .../com/tangem/tap/domain/scanCard/ScanCardProcessor.kt | 3 +-- .../com/tangem/tap/features/disclaimer/DisclaimerType.kt | 2 ++ 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt b/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt index 24c1384e66..70a1526ce3 100644 --- a/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt @@ -11,6 +11,7 @@ import com.tangem.common.doOnFailure import com.tangem.common.doOnSuccess import com.tangem.common.services.Result import com.tangem.core.analytics.Analytics +import com.tangem.datasource.config.ConfigManager import com.tangem.domain.common.CardDTO import com.tangem.domain.common.ScanResponse import com.tangem.domain.common.TapWorkarounds.isTestCard @@ -21,7 +22,6 @@ import com.tangem.tap.common.extensions.dispatchOnMain import com.tangem.tap.common.extensions.safeUpdate import com.tangem.tap.common.extensions.setContext import com.tangem.tap.common.redux.global.GlobalAction -import com.tangem.datasource.config.ConfigManager import com.tangem.tap.domain.extensions.makePrimaryWalletManager import com.tangem.tap.domain.extensions.makeWalletManagersForApp import com.tangem.tap.domain.model.UserWallet @@ -29,6 +29,8 @@ import com.tangem.tap.domain.tokens.models.BlockchainNetwork import com.tangem.tap.domain.walletStores.WalletStoresError import com.tangem.tap.features.demo.isDemoCard import com.tangem.tap.features.details.redux.walletconnect.WalletConnectAction +import com.tangem.tap.features.disclaimer.createDisclaimer +import com.tangem.tap.features.disclaimer.redux.DisclaimerAction import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsAction import com.tangem.tap.features.wallet.models.toBlockchainNetworks import com.tangem.tap.features.wallet.redux.WalletAction @@ -110,8 +112,8 @@ class TapWalletManager { tangemSdkManager.changeDisplayedCardIdNumbersCount(scanResponse) store.state.globalState.feedbackManager?.infoHolder?.setCardInfo(scanResponse) updateConfigManager(scanResponse) - withMainContext { + store.dispatch(DisclaimerAction.SetDisclaimer(card.createDisclaimer())) store.dispatch(WalletAction.UserWalletChanged(userWallet)) store.dispatch(TwinCardsAction.IfTwinsPrepareState(scanResponse)) store.dispatch(WalletConnectAction.ResetState) diff --git a/app/src/main/java/com/tangem/tap/domain/scanCard/ScanCardProcessor.kt b/app/src/main/java/com/tangem/tap/domain/scanCard/ScanCardProcessor.kt index 56e9beaf22..facfc3d62e 100644 --- a/app/src/main/java/com/tangem/tap/domain/scanCard/ScanCardProcessor.kt +++ b/app/src/main/java/com/tangem/tap/domain/scanCard/ScanCardProcessor.kt @@ -20,7 +20,6 @@ import com.tangem.tap.common.extensions.setContext import com.tangem.tap.common.redux.global.GlobalAction import com.tangem.tap.common.redux.navigation.AppScreen import com.tangem.tap.common.redux.navigation.NavigationAction -import com.tangem.tap.features.disclaimer.DisclaimerType import com.tangem.tap.features.disclaimer.createDisclaimer import com.tangem.tap.features.disclaimer.redux.DisclaimerAction import com.tangem.tap.features.disclaimer.redux.DisclaimerCallback @@ -150,7 +149,7 @@ object ScanCardProcessor { crossinline nextHandler: suspend (ScanResponse) -> Unit, crossinline onFailure: suspend (error: TangemError) -> Unit, ) { - val disclaimer = DisclaimerType.get(scanResponse.card).createDisclaimer(scanResponse.card) + val disclaimer = scanResponse.card.createDisclaimer() store.dispatchOnMain(DisclaimerAction.SetDisclaimer(disclaimer)) if (disclaimer.isAccepted()) { diff --git a/app/src/main/java/com/tangem/tap/features/disclaimer/DisclaimerType.kt b/app/src/main/java/com/tangem/tap/features/disclaimer/DisclaimerType.kt index d6af0e0596..b88b4488ab 100644 --- a/app/src/main/java/com/tangem/tap/features/disclaimer/DisclaimerType.kt +++ b/app/src/main/java/com/tangem/tap/features/disclaimer/DisclaimerType.kt @@ -35,6 +35,8 @@ fun DisclaimerType.createDisclaimer(cardDTO: CardDTO): Disclaimer { } } +fun CardDTO.createDisclaimer(): Disclaimer = DisclaimerType.get(this).createDisclaimer(this) + private fun provideDisclaimerDataProvider(cardId: String): DisclaimerDataProvider { return object : DisclaimerDataProvider { override fun getLanguage(): String = Locale.getDefault().language From ff4bbbe7ad09a5bf55ce2fe1bc61e49a467d2b88 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 1 Mar 2023 21:08:01 +0300 Subject: [PATCH 06/17] Updated on 2026-08-14 --- .../tap/features/wallet/redux/WalletData.kt | 47 +++++++++---------- 1 file changed, 23 insertions(+), 24 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletData.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletData.kt index b6ed172e1a..e5edb01fa4 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletData.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletData.kt @@ -77,36 +77,35 @@ data class WalletData( if (currencyData.status == BalanceStatus.SameCurrencyTransactionInProgress) { walletWarnings.add(WalletWarning.TransactionInProgress(currency.currencyName)) } - } - - private fun assembleBlockchainWarnings(walletWarnings: MutableList) { - if (!currency.isBlockchain()) return - - if (existentialDepositString != null) { - val warning = WalletWarning.ExistentialDeposit( - currencyName = currency.currencyName, - edStringValueWithSymbol = "$existentialDepositString ${currency.currencySymbol}", - ) - walletWarnings.add(warning) - } if (walletRent != null) { walletWarnings.add(WalletWarning.Rent(walletRent)) } } - private fun assembleTokenWarnings(walletWarnings: MutableList) { - with(currency) { - if (!isToken()) return + private fun assembleBlockchainWarnings(walletWarnings: MutableList) = with(currency) { + if (!isBlockchain()) return - if (blockchainAmountIsEmpty() && !tokenAmountIsEmpty()) { - walletWarnings.add( - WalletWarning.BalanceNotEnoughForFee( - currencyName = currencyName, - blockchainFullName = blockchain.fullName, - blockchainSymbol = blockchain.currency, - ), - ) - } + if (existentialDepositString != null) { + val warning = WalletWarning.ExistentialDeposit( + currencyName = currencyName, + edStringValueWithSymbol = "$existentialDepositString $currencySymbol", + ) + walletWarnings.add(warning) + } + + } + + private fun assembleTokenWarnings(walletWarnings: MutableList) = with(currency) { + if (!isToken()) return + + if (blockchainAmountIsEmpty() && !tokenAmountIsEmpty()) { + walletWarnings.add( + WalletWarning.BalanceNotEnoughForFee( + currencyName = currencyName, + blockchainFullName = blockchain.fullName, + blockchainSymbol = blockchain.currency, + ), + ) } } From 89ddc0a56abed1fe14bebf882d22d085b2baa6ad Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 1 Mar 2023 21:24:54 +0300 Subject: [PATCH 07/17] Updated on 2026-08-14 --- .../layout/item_currency_wallet_content.xml | 29 +++++++++++-------- 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/app/src/main/res/layout/item_currency_wallet_content.xml b/app/src/main/res/layout/item_currency_wallet_content.xml index aa767d069c..b27a1d1edb 100644 --- a/app/src/main/res/layout/item_currency_wallet_content.xml +++ b/app/src/main/res/layout/item_currency_wallet_content.xml @@ -1,17 +1,17 @@ + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:minHeight="42dp"> + android:id="@+id/guideline_horizontal" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="horizontal" + app:layout_constraintGuide_percent="0.5" /> + tools:text="Transaction in progress and it very long text with some strange status" + tools:visibility="visible" /> + tools:text="46 908 $" + tools:visibility="gone" /> + From a29fdb62e1e1201b974c0389a157b95272f54fa4 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 1 Mar 2023 18:14:07 +0300 Subject: [PATCH 08/17] Updated on 2026-08-14 --- .../main/java/com/tangem/tap/proxy/UserWalletManagerImpl.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/tangem/tap/proxy/UserWalletManagerImpl.kt b/app/src/main/java/com/tangem/tap/proxy/UserWalletManagerImpl.kt index 250bf73562..d8273b39ee 100644 --- a/app/src/main/java/com/tangem/tap/proxy/UserWalletManagerImpl.kt +++ b/app/src/main/java/com/tangem/tap/proxy/UserWalletManagerImpl.kt @@ -144,7 +144,9 @@ class UserWalletManagerImpl( val card = requireNotNull(appStateHolder.getActualCard()) { "card not found" } val blockchainNetwork = BlockchainNetwork(blockchain, card) val walletManager = appStateHolder.walletState?.getWalletManager(blockchainNetwork) - return walletManager?.wallet?.recentTransactions?.lastOrNull()?.hash?.let { HEX_PREFIX + it } + return walletManager?.wallet?.recentTransactions + ?.lastOrNull { it.hash?.isNotEmpty() == true } + ?.hash?.let { HEX_PREFIX + it } } override suspend fun getCurrentWalletTokensBalance( From bec0fad6c63450bce8e6afd24fc88d56520e5a89 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 2 Mar 2023 09:24:04 +0800 Subject: [PATCH 09/17] Updated on 2026-08-14 --- buildSrc/src/main/java/Versions.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/buildSrc/src/main/java/Versions.kt b/buildSrc/src/main/java/Versions.kt index 32ba2da6ab..30e94972db 100644 --- a/buildSrc/src/main/java/Versions.kt +++ b/buildSrc/src/main/java/Versions.kt @@ -61,11 +61,11 @@ object Versions { // endregion Other libraries // region Tangem - const val tangemBlockchainSdk = "develop-168" + const val tangemBlockchainSdk = "develop-171" // const val tangemBlockchainSdk = "0.0.1" // Keep it! - used for local builds const val tangemCardSdk = "develop-191" - // const val tangemCardSgk = "0.0.1" // Keep it! - used for local builds + // const val tangemCardSdk = "0.0.1" // Keep it! - used for local builds // endregion Tangem // region Tools From 8f71e460a2f343ca08dd913e4bfc91c130da71cf Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 28 Feb 2023 14:44:37 +0300 Subject: [PATCH 10/17] Updated on 2026-08-14 --- .../main/java/com/tangem/tap/MainActivity.kt | 34 +++++++++---- .../com/tangem/tap/common/extensions/Store.kt | 24 +++++++-- .../tap/common/redux/global/GlobalAction.kt | 3 ++ .../tap/common/redux/global/GlobalReducer.kt | 6 +++ .../tap/common/redux/global/GlobalState.kt | 4 +- .../com/tangem/tap/domain/TapWalletManager.kt | 3 ++ .../di/UserWalletsListManagerProvider.kt | 2 - .../details/redux/DetailsMiddleware.kt | 51 +++++++++++++++---- .../tap/features/home/redux/HomeMiddleware.kt | 43 ++++++++-------- .../features/onboarding/OnboardingHelper.kt | 27 ++++++++-- .../saveWallet/redux/SaveWalletMiddleware.kt | 42 ++++++++++++--- .../tap/features/wallet/redux/WalletAction.kt | 4 ++ .../tap/features/wallet/redux/WalletState.kt | 1 + .../redux/middlewares/WalletMiddleware.kt | 17 +++++-- .../wallet/redux/reducers/WalletReducer.kt | 5 ++ .../tap/features/wallet/ui/WalletFragment.kt | 16 +++--- .../tap/features/wallet/ui/WalletViewModel.kt | 51 ++++++++++++++++--- 17 files changed, 256 insertions(+), 77 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/MainActivity.kt b/app/src/main/java/com/tangem/tap/MainActivity.kt index c3507df132..14d367046b 100644 --- a/app/src/main/java/com/tangem/tap/MainActivity.kt +++ b/app/src/main/java/com/tangem/tap/MainActivity.kt @@ -19,6 +19,7 @@ import com.tangem.tap.common.OnActivityResultCallback import com.tangem.tap.common.SnackbarHandler import com.tangem.tap.common.extensions.dispatchOnMain import com.tangem.tap.common.redux.NotificationsHandler +import com.tangem.tap.common.redux.global.GlobalAction import com.tangem.tap.common.redux.navigation.AppScreen import com.tangem.tap.common.redux.navigation.NavigationAction import com.tangem.tap.common.shop.googlepay.GooglePayService @@ -27,6 +28,7 @@ import com.tangem.tap.common.shop.googlepay.GooglePayUtil.createPaymentsClient import com.tangem.tap.domain.TangemSdkManager import com.tangem.tap.domain.userWalletList.UserWalletsListManager import com.tangem.tap.domain.userWalletList.di.provideBiometricImplementation +import com.tangem.tap.domain.userWalletList.di.provideRuntimeImplementation import com.tangem.tap.features.onboarding.products.wallet.redux.BackupAction import com.tangem.tap.features.shop.redux.ShopAction import com.tangem.tap.features.welcome.redux.WelcomeAction @@ -45,11 +47,8 @@ import kotlin.coroutines.CoroutineContext lateinit var tangemSdk: TangemSdk lateinit var tangemSdkManager: TangemSdkManager lateinit var backupService: BackupService -lateinit var userWalletsListManager: UserWalletsListManager internal var lockUserWalletsTimer: LockUserWalletsTimer? = null private set -var userWalletsListManagerSafe: UserWalletsListManager? = null - private set var notificationsHandler: NotificationsHandler? = null private val coroutineContext: CoroutineContext @@ -60,6 +59,12 @@ private val mainCoroutineContext: CoroutineContext get() = Job() + Dispatchers.Main + FeatureCoroutineExceptionHandler.create("mainScope") val mainScope = CoroutineScope(mainCoroutineContext) +// TODO: Move to DI +val userWalletsListManagerSafe: UserWalletsListManager? + get() = store.state.globalState.userWalletsListManager +val userWalletsListManager: UserWalletsListManager + get() = userWalletsListManagerSafe!! + @AndroidEntryPoint class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbackHolder { @@ -83,14 +88,10 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac appStateHolder.tangemSdkManager = tangemSdkManager appStateHolder.tangemSdk = tangemSdk backupService = BackupService.init(tangemSdk, this) - userWalletsListManager = UserWalletsListManager.provideBiometricImplementation( - context = applicationContext, - tangemSdkManager = tangemSdkManager, - ) - appStateHolder.userWalletsListManager = userWalletsListManager - userWalletsListManagerSafe = userWalletsListManager lockUserWalletsTimer = LockUserWalletsTimer(owner = this) + initUserWalletsListManager() + store.dispatch( ShopAction.CheckIfGooglePayAvailable( GooglePayService(createPaymentsClient(this), this), @@ -98,6 +99,18 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac ) } + private fun initUserWalletsListManager() { + val manager = if (preferencesStorage.shouldSaveUserWallets) { + UserWalletsListManager.provideBiometricImplementation( + context = applicationContext, + tangemSdkManager = tangemSdkManager, + ) + } else { + UserWalletsListManager.provideRuntimeImplementation() + } + store.dispatch(GlobalAction.UpdateUserWalletsListManager(manager)) + } + private fun systemActions() { WindowCompat.setDecorFitsSystemWindows(window, false) @@ -200,11 +213,12 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac backStackIsEmpty -> { navigateToInitialScreen(intent) } + else -> Unit } } private fun navigateToInitialScreen(intent: Intent?) { - if (userWalletsListManager.hasUserWallets) { + if (store.state.globalState.userWalletsListManager?.hasUserWallets == true) { store.dispatchOnMain(NavigationAction.NavigateTo(AppScreen.Welcome)) store.dispatchOnMain(WelcomeAction.HandleIntentIfNeeded(intent)) } else { diff --git a/app/src/main/java/com/tangem/tap/common/extensions/Store.kt b/app/src/main/java/com/tangem/tap/common/extensions/Store.kt index 69c5f1c085..77ed58315d 100644 --- a/app/src/main/java/com/tangem/tap/common/extensions/Store.kt +++ b/app/src/main/java/com/tangem/tap/common/extensions/Store.kt @@ -2,6 +2,7 @@ package com.tangem.tap.common.extensions import com.tangem.domain.common.ScanResponse import com.tangem.domain.common.extensions.withMainContext +import com.tangem.tap.common.redux.AppState import com.tangem.tap.common.redux.StateDialog import com.tangem.tap.common.redux.global.GlobalAction import com.tangem.tap.common.redux.navigation.NavigationAction @@ -16,10 +17,25 @@ import org.rekotlin.Store /** * Dispatch action with creating the new coroutine with the Main dispatcher + * + * @see dispatchWithMain */ fun Store<*>.dispatchOnMain(action: Action) { scope.launch(Dispatchers.Main) { - store.dispatch(action) + dispatch(action) + } +} + +/** + * Dispatch action on the Main coroutine context + * + * @param action [Action] to be dispatched + * + * @see dispatchOnMain + * */ +suspend fun Store<*>.dispatchWithMain(action: Action) { + withMainContext { + dispatch(action) } } @@ -27,9 +43,8 @@ fun Store<*>.dispatchNotification(resId: Int) { dispatchOnMain(GlobalAction.ShowNotification(resId)) } -@Suppress("UnusedReceiverParameter") -suspend fun Store<*>.onUserWalletSelected(userWallet: UserWallet, refresh: Boolean = false) { - store.state.globalState.tapWalletManager.onWalletSelected(userWallet, refresh) +suspend fun Store.onUserWalletSelected(userWallet: UserWallet, refresh: Boolean = false) { + state.globalState.tapWalletManager.onWalletSelected(userWallet, refresh) } fun Store<*>.dispatchToastNotification(resId: Int) { @@ -77,6 +92,7 @@ suspend fun Store<*>.onCardScanned(scanResponse: ScanResponse) { fun Store<*>.dispatchOpenUrl(url: String) { store.dispatch(NavigationAction.OpenUrl(url)) } + fun Store<*>.dispatchShare(url: String) { store.dispatch(NavigationAction.Share(url)) } \ No newline at end of file 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 c4c8a33527..f0f6e8244c 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 @@ -18,6 +18,7 @@ import com.tangem.tap.common.redux.ToastNotificationAction import com.tangem.tap.domain.TapError import com.tangem.tap.domain.configurable.warningMessage.WarningMessage import com.tangem.tap.domain.configurable.warningMessage.WarningMessagesManager +import com.tangem.tap.domain.userWalletList.UserWalletsListManager import com.tangem.tap.features.details.redux.SecurityOption import org.rekotlin.Action @@ -105,4 +106,6 @@ sealed class GlobalAction : Action { object FetchUserCountry : GlobalAction() { data class Success(val countryCode: String) : GlobalAction() } + + data class UpdateUserWalletsListManager(val manager: UserWalletsListManager) : GlobalAction() } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalReducer.kt b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalReducer.kt index 7934a195f6..3ed3751c74 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalReducer.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalReducer.kt @@ -84,6 +84,12 @@ fun globalReducer(action: Action, state: AppState, appStateHolder: AppStateHolde is GlobalAction.FetchUserCountry.Success -> globalState.copy( userCountryCode = action.countryCode, ) + is GlobalAction.UpdateUserWalletsListManager -> { + appStateHolder.userWalletsListManager = action.manager + globalState.copy( + userWalletsListManager = action.manager, + ) + } else -> globalState } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalState.kt b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalState.kt index 845715809d..56059c6799 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalState.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalState.kt @@ -1,13 +1,14 @@ package com.tangem.tap.common.redux.global +import com.tangem.datasource.config.ConfigManager import com.tangem.domain.common.ScanResponse import com.tangem.tap.common.entities.FiatCurrency import com.tangem.tap.common.feedback.FeedbackManager import com.tangem.tap.common.redux.StateDialog import com.tangem.tap.domain.PayIdManager import com.tangem.tap.domain.TapWalletManager -import com.tangem.datasource.config.ConfigManager import com.tangem.tap.domain.configurable.warningMessage.WarningMessagesManager +import com.tangem.tap.domain.userWalletList.UserWalletsListManager import com.tangem.tap.features.onboarding.OnboardingManager import com.tangem.tap.network.exchangeServices.CurrencyExchangeManager import org.rekotlin.StateType @@ -26,6 +27,7 @@ data class GlobalState( val dialog: StateDialog? = null, val exchangeManager: CurrencyExchangeManager = CurrencyExchangeManager.dummy(), val userCountryCode: String? = null, + val userWalletsListManager: UserWalletsListManager? = null, ) : StateType typealias CryptoCurrencyName = String diff --git a/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt b/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt index 70a1526ce3..4b34bbb8e7 100644 --- a/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt @@ -36,6 +36,7 @@ import com.tangem.tap.features.wallet.models.toBlockchainNetworks import com.tangem.tap.features.wallet.redux.WalletAction import com.tangem.tap.features.wallet.redux.middlewares.handleBasicAnalyticsEvent import com.tangem.tap.network.NetworkConnectivity +import com.tangem.tap.preferencesStorage import com.tangem.tap.store import com.tangem.tap.tangemSdkManager import com.tangem.tap.userTokensRepository @@ -113,8 +114,10 @@ class TapWalletManager { store.state.globalState.feedbackManager?.infoHolder?.setCardInfo(scanResponse) updateConfigManager(scanResponse) withMainContext { + // Order is important store.dispatch(DisclaimerAction.SetDisclaimer(card.createDisclaimer())) store.dispatch(WalletAction.UserWalletChanged(userWallet)) + store.dispatch(WalletAction.UpdateCanSaveUserWallets(preferencesStorage.shouldSaveUserWallets)) store.dispatch(TwinCardsAction.IfTwinsPrepareState(scanResponse)) store.dispatch(WalletConnectAction.ResetState) store.dispatch(GlobalAction.SaveScanResponse(scanResponse)) diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/di/UserWalletsListManagerProvider.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/di/UserWalletsListManagerProvider.kt index 163c0cdc60..108547e8b2 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/di/UserWalletsListManagerProvider.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/di/UserWalletsListManagerProvider.kt @@ -71,8 +71,6 @@ fun UserWalletsListManager.Companion.provideBiometricImplementation( ) } -// TODO: Will be used in further MR's -@Suppress("unused") fun UserWalletsListManager.Companion.provideRuntimeImplementation(): UserWalletsListManager { return RuntimeUserWalletsListManager() } \ No newline at end of file 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 d5032c7ce7..4ed3ac44b3 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 @@ -4,6 +4,7 @@ import com.tangem.common.CompletionResult import com.tangem.common.core.TangemSdkError import com.tangem.common.doOnFailure import com.tangem.common.doOnSuccess +import com.tangem.common.extensions.guard import com.tangem.common.flatMap import com.tangem.core.analytics.Analytics import com.tangem.domain.common.ScanResponse @@ -12,6 +13,7 @@ import com.tangem.tap.common.analytics.events.AnalyticsParam import com.tangem.tap.common.analytics.events.Settings import com.tangem.tap.common.extensions.dispatchDialogShow import com.tangem.tap.common.extensions.dispatchOnMain +import com.tangem.tap.common.extensions.dispatchWithMain import com.tangem.tap.common.extensions.onUserWalletSelected import com.tangem.tap.common.redux.AppDialog import com.tangem.tap.common.redux.AppState @@ -20,11 +22,16 @@ import com.tangem.tap.common.redux.navigation.AppScreen import com.tangem.tap.common.redux.navigation.NavigationAction import com.tangem.tap.domain.model.builders.UserWalletBuilder import com.tangem.tap.domain.model.builders.UserWalletIdBuilder +import com.tangem.tap.domain.userWalletList.UserWalletsListManager +import com.tangem.tap.domain.userWalletList.di.provideBiometricImplementation +import com.tangem.tap.domain.userWalletList.di.provideRuntimeImplementation import com.tangem.tap.domain.userWalletList.isLockedSync import com.tangem.tap.domain.userWalletList.lockIfLockable import com.tangem.tap.features.demo.DemoHelper import com.tangem.tap.features.onboarding.products.twins.redux.CreateTwinWalletMode import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsAction +import com.tangem.tap.features.wallet.redux.WalletAction +import com.tangem.tap.foregroundActivityObserver import com.tangem.tap.preferencesStorage import com.tangem.tap.scope import com.tangem.tap.store @@ -236,7 +243,7 @@ class DetailsMiddleware { delay(timeMillis = 100) } } - store.dispatchOnMain( + store.dispatchWithMain( DetailsAction.AppSettings.BiometricsStatusChanged( needEnrollBiometrics = tangemSdkManager.needEnrollBiometrics, ), @@ -252,13 +259,13 @@ class DetailsMiddleware { private fun toggleSaveWallets(state: DetailsState, enable: Boolean) = scope.launch { // Nothing to change if (preferencesStorage.shouldSaveUserWallets == enable) { - store.dispatchOnMain(DetailsAction.AppSettings.SwitchPrivacySetting.Success) + store.dispatchWithMain(DetailsAction.AppSettings.SwitchPrivacySetting.Success) return@launch } toggleSaveWallets(state.scanResponse, enable) .doOnFailure { - store.dispatchOnMain( + store.dispatchWithMain( DetailsAction.AppSettings.SwitchPrivacySetting.Failure( prevState = !enable, setting = AppSetting.SaveWallets, @@ -266,7 +273,7 @@ class DetailsMiddleware { ) } .doOnSuccess { - store.dispatchOnMain(DetailsAction.AppSettings.SwitchPrivacySetting.Success) + store.dispatchWithMain(DetailsAction.AppSettings.SwitchPrivacySetting.Success) } } @@ -284,13 +291,13 @@ class DetailsMiddleware { private fun toggleSaveAccessCodes(state: DetailsState, enable: Boolean) = scope.launch { // Nothing to change if (preferencesStorage.shouldSaveAccessCodes == enable) { - store.dispatchOnMain(DetailsAction.AppSettings.SwitchPrivacySetting.Success) + store.dispatchWithMain(DetailsAction.AppSettings.SwitchPrivacySetting.Success) return@launch } toggleSaveAccessCodes(state.scanResponse, state.appSettingsState.saveWallets, enable) .doOnFailure { - store.dispatchOnMain( + store.dispatchWithMain( DetailsAction.AppSettings.SwitchPrivacySetting.Failure( prevState = !enable, setting = AppSetting.SaveAccessCode, @@ -298,7 +305,7 @@ class DetailsMiddleware { ) } .doOnSuccess { - store.dispatchOnMain(DetailsAction.AppSettings.SwitchPrivacySetting.Success) + store.dispatchWithMain(DetailsAction.AppSettings.SwitchPrivacySetting.Success) } } @@ -327,6 +334,8 @@ class DetailsMiddleware { TangemSdkError.ExceptionError(IllegalStateException("scanResponse is null")), ) + updateUserWalletsListManager(enableUserWalletsSaving = true) + return userWalletsListManager.save(userWallet) .flatMap { if (enableAccessCodesSaving) { @@ -341,7 +350,7 @@ class DetailsMiddleware { preferencesStorage.shouldShowSaveUserWalletScreen = false preferencesStorage.shouldSaveUserWallets = true - store.onUserWalletSelected(userWallet) + store.dispatchWithMain(WalletAction.UpdateCanSaveUserWallets(canSaveUserWallets = true)) } .doOnFailure { error -> Timber.e(error, "Unable to save user wallet") @@ -354,9 +363,11 @@ class DetailsMiddleware { .doOnSuccess { Analytics.send(Settings.AppSettings.SaveWalletSwitcherChanged(AnalyticsParam.OnOffState.Off)) deleteSavedAccessCodes() + updateUserWalletsListManager(enableUserWalletsSaving = false) preferencesStorage.shouldSaveUserWallets = false - store.dispatchOnMain(NavigationAction.PopBackTo(AppScreen.Home)) + store.dispatchWithMain(WalletAction.UpdateCanSaveUserWallets(canSaveUserWallets = true)) + store.dispatchWithMain(NavigationAction.PopBackTo(AppScreen.Home)) } .doOnFailure { error -> Timber.e(error, "Unable to delete saved wallets") @@ -388,5 +399,27 @@ class DetailsMiddleware { Timber.e(error, "Unable to delete saved access codes") } } + + private suspend fun updateUserWalletsListManager(enableUserWalletsSaving: Boolean) { + val manager = if (enableUserWalletsSaving) { + createBiometricsUserWalletsManager() ?: return + } else { + UserWalletsListManager.provideRuntimeImplementation() + } + + store.dispatchWithMain(GlobalAction.UpdateUserWalletsListManager(manager)) + } + + private fun createBiometricsUserWalletsManager(): UserWalletsListManager? { + val context = foregroundActivityObserver.foregroundActivity?.applicationContext.guard { + Timber.e(IllegalStateException("No activities in foreground")) + return null + } + + return UserWalletsListManager.provideBiometricImplementation( + context = context, + tangemSdkManager = tangemSdkManager, + ) + } } } \ No newline at end of file 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 72c24acdb4..0ea4b412cf 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,13 +5,13 @@ import com.tangem.common.doOnResult import com.tangem.common.doOnSuccess import com.tangem.core.analytics.Analytics import com.tangem.core.analytics.AnalyticsEvent +import com.tangem.domain.common.ScanResponse import com.tangem.tap.common.analytics.events.IntroductionProcess import com.tangem.tap.common.analytics.events.Shop import com.tangem.tap.common.entities.IndeterminateProgressButton import com.tangem.tap.common.extensions.dispatchOnMain import com.tangem.tap.common.extensions.dispatchOpenUrl import com.tangem.tap.common.extensions.eraseContext -import com.tangem.tap.common.extensions.onCardScanned import com.tangem.tap.common.extensions.onUserWalletSelected import com.tangem.tap.common.redux.AppState import com.tangem.tap.common.redux.global.GlobalAction @@ -94,29 +94,32 @@ private fun readCard(analyticsEvent: AnalyticsEvent?) = scope.launch { changeButtonState(ButtonState.ENABLED) }, onSuccess = { scanResponse -> - scope.launch { - if (preferencesStorage.shouldSaveUserWallets) { - val userWallet = UserWalletBuilder(scanResponse).build() ?: return@launch - userWalletsListManager.save(userWallet) - .doOnFailure { error -> - Timber.e(error, "Unable to save user wallet") - store.onCardScanned(scanResponse) - } - .doOnSuccess { - scope.launch { store.onUserWalletSelected(userWallet) } - } - .doOnResult { - navigateTo(AppScreen.Wallet) - } - } else { - store.onCardScanned(scanResponse) - navigateTo(AppScreen.Wallet) - } - } + proceedWithScanResponse(scanResponse) }, ) } +fun proceedWithScanResponse(scanResponse: ScanResponse) { + scope.launch { + val userWallet = UserWalletBuilder(scanResponse).build() + if (userWallet == null) { + Timber.e("User wallet not created") + return@launch + } + + userWalletsListManager.save(userWallet) + .doOnFailure { error -> + Timber.e(error, "Unable to save user wallet") + } + .doOnSuccess { + scope.launch { store.onUserWalletSelected(userWallet) } + } + .doOnResult { + navigateTo(AppScreen.Wallet) + } + } +} + private suspend fun navigateTo(appScreen: AppScreen) { store.dispatchOnMain(NavigationAction.NavigateTo(appScreen)) delay(timeMillis = 200) 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 e0d51ccbf7..426d274352 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 @@ -1,21 +1,27 @@ package com.tangem.tap.features.onboarding +import com.tangem.common.doOnFailure +import com.tangem.common.doOnSuccess +import com.tangem.common.extensions.guard import com.tangem.core.analytics.Analytics import com.tangem.domain.common.ProductType import com.tangem.domain.common.ScanResponse import com.tangem.tap.common.extensions.dispatchOnMain -import com.tangem.tap.common.extensions.onCardScanned +import com.tangem.tap.common.extensions.onUserWalletSelected import com.tangem.tap.common.extensions.removeContext import com.tangem.tap.common.extensions.setContext import com.tangem.tap.common.redux.navigation.AppScreen import com.tangem.tap.common.redux.navigation.NavigationAction +import com.tangem.tap.domain.model.builders.UserWalletBuilder import com.tangem.tap.features.saveWallet.redux.SaveWalletAction import com.tangem.tap.preferencesStorage import com.tangem.tap.scope import com.tangem.tap.store import com.tangem.tap.tangemSdkManager +import com.tangem.tap.userWalletsListManager import kotlinx.coroutines.delay import kotlinx.coroutines.launch +import timber.log.Timber /** [REDACTED_AUTHOR] @@ -75,7 +81,7 @@ object OnboardingHelper { // then open save wallet screen tangemSdkManager.canUseBiometry && preferencesStorage.shouldShowSaveUserWalletScreen -> scope.launch { - store.onCardScanned(scanResponse) + proceedWithScanResponse(scanResponse) delay(timeMillis = 1_200) @@ -90,7 +96,7 @@ object OnboardingHelper { } // If device has no biometry and save wallet screen has been shown, then go through old scenario else -> scope.launch { - store.onCardScanned(scanResponse) + proceedWithScanResponse(scanResponse) } } @@ -100,4 +106,19 @@ object OnboardingHelper { fun onInterrupted() { Analytics.removeContext() } + + private suspend fun proceedWithScanResponse(scanResponse: ScanResponse) { + val userWallet = UserWalletBuilder(scanResponse).build().guard { + Timber.e("User wallet not created") + return + } + + userWalletsListManager.save(userWallet) + .doOnFailure { error -> + Timber.e(error, "Unable to save user wallet") + } + .doOnSuccess { + scope.launch { store.onUserWalletSelected(userWallet) } + } + } } \ No newline at end of file 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 5eaa50d8bc..921894565b 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 @@ -1,19 +1,26 @@ package com.tangem.tap.features.saveWallet.redux import com.tangem.common.CompletionResult +import com.tangem.common.core.TangemSdkError import com.tangem.common.doOnFailure import com.tangem.common.doOnSuccess +import com.tangem.common.extensions.guard import com.tangem.common.flatMap import com.tangem.core.analytics.Analytics import com.tangem.tap.common.analytics.events.AnalyticsParam import com.tangem.tap.common.analytics.events.MainScreen import com.tangem.tap.common.analytics.events.Onboarding import com.tangem.tap.common.extensions.dispatchOnMain -import com.tangem.tap.common.extensions.onUserWalletSelected +import com.tangem.tap.common.extensions.dispatchWithMain import com.tangem.tap.common.redux.AppState +import com.tangem.tap.common.redux.global.GlobalAction import com.tangem.tap.common.redux.navigation.AppScreen import com.tangem.tap.common.redux.navigation.NavigationAction import com.tangem.tap.domain.model.builders.UserWalletBuilder +import com.tangem.tap.domain.userWalletList.UserWalletsListManager +import com.tangem.tap.domain.userWalletList.di.provideBiometricImplementation +import com.tangem.tap.features.wallet.redux.WalletAction +import com.tangem.tap.foregroundActivityObserver import com.tangem.tap.preferencesStorage import com.tangem.tap.scope import com.tangem.tap.store @@ -21,6 +28,7 @@ import com.tangem.tap.tangemSdkManager import com.tangem.tap.userWalletsListManager import kotlinx.coroutines.launch import org.rekotlin.Middleware +import timber.log.Timber internal class SaveWalletMiddleware { val middleware: Middleware = { _, stateProvider -> @@ -86,16 +94,19 @@ internal class SaveWalletMiddleware { } scope.launch { - val userWallet = UserWalletBuilder(scanResponse) - .backupCardsIds(state.backupInfo?.backupCardsIds) - .build() ?: return@launch + val userWallet = userWalletsListManager.selectedUserWalletSync + ?: UserWalletBuilder(scanResponse) + .backupCardsIds(state.backupInfo?.backupCardsIds) + .build() ?: return@launch + + provideBiometricUserWalletsListManager() val isFirstSavedWallet = !userWalletsListManager.hasUserWallets saveAccessCodeIfNeeded(state.backupInfo?.accessCode, userWallet.cardsInWallet) .flatMap { userWalletsListManager.save(userWallet, canOverride = true) } .doOnFailure { error -> - store.dispatchOnMain(SaveWalletAction.Save.Error(error)) + store.dispatchWithMain(SaveWalletAction.Save.Error(error)) } .doOnSuccess { preferencesStorage.shouldSaveUserWallets = true @@ -104,14 +115,29 @@ internal class SaveWalletMiddleware { preferencesStorage.shouldSaveAccessCodes = isFirstSavedWallet || preferencesStorage.shouldSaveAccessCodes - store.dispatchOnMain(SaveWalletAction.Save.Success) + store.dispatchWithMain(SaveWalletAction.Save.Success) - store.dispatchOnMain(NavigationAction.PopBackTo(AppScreen.Wallet)) - store.onUserWalletSelected(userWallet) + store.dispatchWithMain(NavigationAction.PopBackTo(AppScreen.Wallet)) + store.dispatchWithMain(WalletAction.UpdateCanSaveUserWallets(canSaveUserWallets = true)) } } } + private suspend fun provideBiometricUserWalletsListManager() { + val context = foregroundActivityObserver.foregroundActivity?.applicationContext.guard { + val error = IllegalStateException("No activities in foreground") + Timber.e(error) + store.dispatchWithMain(SaveWalletAction.Save.Error(TangemSdkError.ExceptionError(error))) + return + } + val manager = UserWalletsListManager.provideBiometricImplementation( + context = context, + tangemSdkManager = tangemSdkManager, + ) + + store.dispatchWithMain(GlobalAction.UpdateUserWalletsListManager(manager)) + } + private fun dismiss(state: SaveWalletState) { if (state.backupInfo != null) { // TODO: Remove after onboarding refactoring diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletAction.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletAction.kt index f839eb03fe..a305989a17 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletAction.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletAction.kt @@ -24,6 +24,10 @@ import java.math.BigDecimal sealed class WalletAction : Action { + object PopBackToInitialScreen : WalletAction() + + data class UpdateCanSaveUserWallets(val canSaveUserWallets: Boolean) : WalletAction() + data class ResetState(val newCard: CardDTO) : WalletAction() data class SetIfTestnetCard(val isTestnet: Boolean) : WalletAction() diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletState.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletState.kt index 973966bc4f..23c86b2a7e 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletState.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletState.kt @@ -45,6 +45,7 @@ data class WalletState( val derivationsCheckIsScheduled: Boolean = false, val loadingUserTokens: Boolean = false, val walletCardsCount: Int? = null, + val canSaveUserWallets: Boolean = false, ) : StateType { val walletsDataFromStores: List diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt index b1d86b23f1..d62cd3d034 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt @@ -38,6 +38,7 @@ import com.tangem.tap.domain.loadedRates import com.tangem.tap.domain.model.WalletDataModel import com.tangem.tap.domain.model.WalletStoreModel import com.tangem.tap.domain.model.builders.UserWalletIdBuilder +import com.tangem.tap.domain.userWalletList.lockIfLockable import com.tangem.tap.features.demo.DemoHelper import com.tangem.tap.features.home.redux.HomeAction import com.tangem.tap.features.send.redux.PrepareSendScreen @@ -300,7 +301,7 @@ class WalletMiddleware { showSaveWalletIfNeeded() } is WalletAction.ChangeWallet -> { - changeWallet() + changeWallet(walletState) } is WalletAction.UserWalletChanged -> Unit is WalletAction.WalletStoresChanged -> { @@ -311,6 +312,16 @@ class WalletMiddleware { tryToShowAppRatingWarning(action.walletStores) } is WalletAction.TotalFiatBalanceChanged -> Unit + is WalletAction.PopBackToInitialScreen -> { + userWalletsListManager.lockIfLockable() + val screen = if (walletState.canSaveUserWallets) { + AppScreen.Welcome + } else { + AppScreen.Home + } + + store.dispatchOnMain(NavigationAction.PopBackTo(screen)) + } } } @@ -375,9 +386,9 @@ class WalletMiddleware { } } - private fun changeWallet() { + private fun changeWallet(state: WalletState) { when { - userWalletsListManager.hasUserWallets -> { + state.canSaveUserWallets -> { Analytics.send(MainScreen.ButtonMyWallets()) store.dispatchOnMain(NavigationAction.NavigateTo(AppScreen.WalletSelector)) } diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/WalletReducer.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/WalletReducer.kt index c1157544cb..d79d7a6339 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/WalletReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/WalletReducer.kt @@ -367,6 +367,11 @@ private fun internalReduce(action: Action, state: AppState, appStateHolder: AppS ), ) } + is WalletAction.UpdateCanSaveUserWallets -> { + newState = newState.copy( + canSaveUserWallets = action.canSaveUserWallets, + ) + } else -> Unit } appStateHolder.walletState = newState diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletFragment.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletFragment.kt index e35f31191d..3e99ac95ed 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletFragment.kt @@ -32,7 +32,6 @@ import com.tangem.tap.common.redux.navigation.NavigationAction import com.tangem.tap.domain.configurable.warningMessage.WarningMessage import com.tangem.tap.domain.statePrinter.printScanResponseState import com.tangem.tap.domain.statePrinter.printWalletState -import com.tangem.tap.domain.userWalletList.lockIfLockable import com.tangem.tap.features.details.redux.DetailsAction import com.tangem.tap.features.wallet.redux.ErrorType import com.tangem.tap.features.wallet.redux.ProgressState @@ -44,7 +43,6 @@ import com.tangem.tap.features.wallet.ui.wallet.SingleWalletView import com.tangem.tap.features.wallet.ui.wallet.WalletView import com.tangem.tap.features.wallet.ui.wallet.saltPay.SaltPayWalletView import com.tangem.tap.store -import com.tangem.tap.userWalletsListManager import com.tangem.wallet.BuildConfig import com.tangem.wallet.R import com.tangem.wallet.databinding.FragmentWalletBinding @@ -82,13 +80,7 @@ class WalletFragment : Fragment(R.layout.fragment_wallet), StoreSubscriber { + private var observeWalletStoresUpdatesJob: Job? = null + set(value) { + field?.cancel() + field = value + } + + init { + subscribeToUserWalletsListManagerUpdates() + } + + override fun onCleared() { + store.unsubscribe(this) + } + + override fun newState(state: UserWalletsListManager?) { + // Restarting observing of wallet store updates when the manager changes + if (state != null) { + bootstrapSelectedWalletStoresChanges(state) + } + } -internal class WalletViewModel : ViewModel() { fun launch() { - bootstrapSelectedWalletStoresChanges() + val manager = store.state.globalState.userWalletsListManager + if (manager != null) { + bootstrapSelectedWalletStoresChanges(manager) + } bootstrapShowSaveWalletIfNeeded() } - private fun bootstrapSelectedWalletStoresChanges() { - userWalletsListManager.selectedUserWallet + private fun bootstrapSelectedWalletStoresChanges(manager: UserWalletsListManager) { + observeWalletStoresUpdatesJob = manager.selectedUserWallet .map { it.walletId } - .distinctUntilChanged() .flatMapLatest { selectedUserWalletId -> walletStoresManager.get(selectedUserWalletId) } @@ -40,4 +67,14 @@ internal class WalletViewModel : ViewModel() { store.dispatchOnMain(WalletAction.ShowSaveWalletIfNeeded) } } + + private fun subscribeToUserWalletsListManagerUpdates() { + store.subscribe(this) { appState -> + appState + .skip { old, new -> + old.globalState.userWalletsListManager == new.globalState.userWalletsListManager + } + .select { it.globalState.userWalletsListManager } + } + } } \ No newline at end of file From 8b81f309c81a77d1f50b7866f9c2e6609279a529 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 28 Feb 2023 19:22:37 +0300 Subject: [PATCH 11/17] Updated on 2026-08-14 --- .../details/redux/DetailsMiddleware.kt | 2 - .../send/redux/middlewares/SendMiddleware.kt | 4 +- .../features/tokens/redux/TokensMiddleware.kt | 67 +++--------------- .../middlewares/AppCurrencyMiddleware.kt | 6 +- .../middlewares/MultiWalletMiddleware.kt | 24 ++----- .../redux/middlewares/WalletMiddleware.kt | 12 +--- .../wallet/ui/WalletDetailsFragment.kt | 12 +--- .../tangem/tap/proxy/DerivationManagerImpl.kt | 12 ++-- .../tangem/tap/proxy/UserWalletManagerImpl.kt | 68 +------------------ .../com/tangem/tap/proxy/di/ProxyModule.kt | 2 - 10 files changed, 30 insertions(+), 179 deletions(-) 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 4ed3ac44b3..5609112906 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 @@ -26,7 +26,6 @@ import com.tangem.tap.domain.userWalletList.UserWalletsListManager import com.tangem.tap.domain.userWalletList.di.provideBiometricImplementation import com.tangem.tap.domain.userWalletList.di.provideRuntimeImplementation import com.tangem.tap.domain.userWalletList.isLockedSync -import com.tangem.tap.domain.userWalletList.lockIfLockable import com.tangem.tap.features.demo.DemoHelper import com.tangem.tap.features.onboarding.products.twins.redux.CreateTwinWalletMode import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsAction @@ -141,7 +140,6 @@ class DetailsMiddleware { store.onUserWalletSelected(selectedUserWallet) } } else { - userWalletsListManager.lockIfLockable() store.dispatchOnMain(NavigationAction.PopBackTo(AppScreen.Home)) } } diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/SendMiddleware.kt b/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/SendMiddleware.kt index c3431d6d45..d4785af2f5 100644 --- a/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/SendMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/SendMiddleware.kt @@ -33,7 +33,6 @@ import com.tangem.tap.domain.TangemSigner import com.tangem.tap.domain.TapError import com.tangem.tap.domain.configurable.warningMessage.WarningMessage import com.tangem.tap.domain.extensions.minimalAmount -import com.tangem.tap.domain.tokens.models.BlockchainNetwork import com.tangem.tap.features.demo.DemoTransactionSender import com.tangem.tap.features.demo.isDemoCard import com.tangem.tap.features.send.redux.AddressPayIdActionUi @@ -61,6 +60,7 @@ import kotlinx.coroutines.delay import kotlinx.coroutines.launch import org.rekotlin.Action import org.rekotlin.Middleware +import timber.log.Timber import java.util.* /** @@ -344,6 +344,6 @@ private suspend fun updateWallet(walletManager: WalletManager) { ), ) } else { - store.dispatchOnMain(WalletAction.LoadWallet(BlockchainNetwork.fromWalletManager(walletManager))) + Timber.e("Unable to update wallet, no user wallet selected") } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensMiddleware.kt b/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensMiddleware.kt index 4e71d8a002..7d9b1027ed 100644 --- a/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensMiddleware.kt @@ -1,7 +1,6 @@ package com.tangem.tap.features.tokens.redux import com.tangem.blockchain.common.Blockchain -import com.tangem.blockchain.common.DerivationParams import com.tangem.blockchain.common.DerivationStyle import com.tangem.common.CompletionResult import com.tangem.common.card.EllipticCurve @@ -30,11 +29,8 @@ import com.tangem.tap.common.redux.global.GlobalAction import com.tangem.tap.common.redux.navigation.AppScreen import com.tangem.tap.common.redux.navigation.NavigationAction import com.tangem.tap.domain.TapError -import com.tangem.tap.domain.extensions.makeWalletManagerForApp import com.tangem.tap.domain.tokens.LoadAvailableCoinsService -import com.tangem.tap.domain.tokens.models.BlockchainNetwork import com.tangem.tap.features.wallet.models.Currency -import com.tangem.tap.features.wallet.redux.WalletAction import com.tangem.tap.scope import com.tangem.tap.store import com.tangem.tap.tangemSdkManager @@ -44,6 +40,7 @@ import com.tangem.utils.coroutines.AppCoroutineDispatcherProvider import kotlinx.coroutines.delay import kotlinx.coroutines.launch import org.rekotlin.Middleware +import timber.log.Timber @Suppress("LargeClass") class TokensMiddleware { @@ -191,7 +188,7 @@ class TokensMiddleware { } } - fun deriveMissingBlockchains( + private fun deriveMissingBlockchains( scanResponse: ScanResponse, currencyList: List, onSuccess: (ScanResponse) -> Unit, @@ -268,15 +265,11 @@ class TokensMiddleware { return DerivationData( derivations = mapKeyOfWalletPublicKey to toDerive, - alreadyDerivedKeys = alreadyDerivedKeys, - mapKeyOfWalletPublicKey = mapKeyOfWalletPublicKey, ) } private class DerivationData( val derivations: Pair>, - val alreadyDerivedKeys: ExtendedPublicKeysMap, - val mapKeyOfWalletPublicKey: ByteArrayKey, ) private fun submitAdd(scanResponse: ScanResponse, currencyList: List) { @@ -297,59 +290,17 @@ class TokensMiddleware { } } } else { - val factory = store.state.globalState.tapWalletManager.walletManagerFactory - val derivationStyle = scanResponse.card.derivationStyle - - val addActions = currencyList.mapIndexedNotNull { index, currency -> - when (currency) { - is Currency.Blockchain -> { - val derivationPath = currency.derivationPath?.let { DerivationPath(it) } - val derivationParams = derivationStyle?.let { - when (derivationPath) { - null -> DerivationParams.Default(derivationStyle) - else -> DerivationParams.Custom(derivationPath) - } - } - val walletManager = factory.makeWalletManagerForApp( - scanResponse = scanResponse, - blockchain = currency.blockchain, - derivationParams = derivationParams, - ) ?: return@mapIndexedNotNull null - WalletAction.MultiWallet.AddBlockchain( - blockchain = BlockchainNetwork.fromWalletManager(walletManager), - walletManager = walletManager, - save = index == currencyList.lastIndex, - ) - } - is Currency.Token -> { - val rawDerivationPath = currency.derivationPath - ?: currency.blockchain.derivationPath(derivationStyle)?.rawPath - val blockchainNetwork = - BlockchainNetwork(currency.blockchain, rawDerivationPath, listOf(currency.token)) - WalletAction.MultiWallet.AddToken( - token = currency.token, - blockchain = blockchainNetwork, - save = index == currencyList.lastIndex, - ) - } - } - } - addActions.forEach { store.dispatchOnMain(it) } + Timber.e("Unable to add currencies, no user wallet selected") } } private suspend fun removeCurrenciesIfNeeded(currencies: List) { - when { - currencies.isEmpty() -> Unit - userWalletsListManager.hasUserWallets -> { - walletCurrenciesManager.removeCurrencies( - userWallet = userWalletsListManager.selectedUserWalletSync!!, - currenciesToRemove = currencies, - ) - } - else -> { - store.dispatch(WalletAction.MultiWallet.RemoveWallets(currencies)) - } + if (currencies.isEmpty()) return + val selectedUserWallet = userWalletsListManager.selectedUserWalletSync + if (selectedUserWallet != null) { + walletCurrenciesManager.removeCurrencies(selectedUserWallet, currencies) + } else { + Timber.e("Unable to remove currencies, no user wallet selected") } } diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/AppCurrencyMiddleware.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/AppCurrencyMiddleware.kt index a2b7aa110e..ad7e1d4914 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/AppCurrencyMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/AppCurrencyMiddleware.kt @@ -1,7 +1,7 @@ package com.tangem.tap.features.wallet.redux.middlewares -import com.tangem.datasource.api.tangemTech.models.CurrenciesResponse import com.tangem.core.analytics.Analytics +import com.tangem.datasource.api.tangemTech.models.CurrenciesResponse import com.tangem.tap.common.analytics.events.AnalyticsParam import com.tangem.tap.common.analytics.events.MainScreen import com.tangem.tap.common.entities.FiatCurrency @@ -18,6 +18,7 @@ import com.tangem.tap.scope import com.tangem.tap.store import com.tangem.tap.userWalletsListManager import kotlinx.coroutines.launch +import timber.log.Timber class AppCurrencyMiddleware( private val walletRepository: WalletRepository, @@ -72,8 +73,7 @@ class AppCurrencyMiddleware( tapWalletManager.loadData(selectedUserWallet, refresh = true) } } else { - tapWalletManager.rates.clear() - store.dispatch(WalletAction.LoadFiatRate()) + Timber.e("Unable to select currency, no user wallet selected") } } diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/MultiWalletMiddleware.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/MultiWalletMiddleware.kt index 2749e3db13..65a19b3d79 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/MultiWalletMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/MultiWalletMiddleware.kt @@ -38,6 +38,7 @@ import com.tangem.tap.userWalletsListManager import com.tangem.tap.walletCurrenciesManager import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch +import timber.log.Timber import java.math.BigDecimal class MultiWalletMiddleware { @@ -139,20 +140,7 @@ class MultiWalletMiddleware { ) } } else { - val currency = action.currency - val card = globalState.scanResponse?.card.guard { - store.dispatchErrorNotification(TapError.UnsupportedState("card is NULL")) - store.dispatch(NavigationAction.PopBackTo(AppScreen.Home)) - return - } - var currencies = walletState?.currencies ?: emptyList() - currencies = currencies.filterNot { it == currency } - if (currency.isBlockchain()) { - currencies.filter { - it.blockchain == currency.blockchain && it.derivationPath == currency.derivationPath - } - } - scope.launch { userTokensRepository.saveUserTokens(card, currencies) } + Timber.e("Unable to remove wallet, no user wallet selected") } } is WalletAction.MultiWallet.RemoveWallets -> { @@ -178,11 +166,11 @@ class MultiWalletMiddleware { scope.launch { handleBasicAnalyticsEvent() } } is WalletAction.MultiWallet.ScanToGetDerivations -> { - val selectedWallet = userWalletsListManager.selectedUserWalletSync - if (selectedWallet != null) { - scanAndUpdateCard(selectedWallet, walletState) + val selectedUserWallet = userWalletsListManager.selectedUserWalletSync + if (selectedUserWallet != null) { + scanAndUpdateCard(selectedUserWallet, walletState) } else { - store.dispatch(WalletAction.Scan(onScanSuccessEvent = null)) + Timber.e("Unable to scan to get derivations, no user wallet selected") } } else -> {} diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt index d62cd3d034..0ba437fdb6 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt @@ -239,13 +239,7 @@ class WalletMiddleware { refresh = action is WalletAction.LoadData.Refresh, ) } else { - val scanResponse = globalState.scanResponse ?: return@launch - - if (walletState.walletsDataFromStores.isNotEmpty()) { - globalState.tapWalletManager.reloadData(scanResponse) - } else { - globalState.tapWalletManager.loadData(scanResponse) - } + Timber.e("Unable to load/refresh wallets data, no user wallet selected") } } } @@ -257,9 +251,7 @@ class WalletMiddleware { if (selectedUserWallet != null) { scope.launch { globalState.tapWalletManager.loadData(selectedUserWallet) } } else { - globalState.scanResponse?.let { scanNoteResponse -> - scope.launch { globalState.tapWalletManager.loadData(scanNoteResponse) } - } + Timber.e("Unable to proceed with changed network state, no user wallet selected") } } is WalletAction.CopyAddress -> { diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletDetailsFragment.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletDetailsFragment.kt index fcabcee21b..3ea64226b5 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletDetailsFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletDetailsFragment.kt @@ -30,7 +30,6 @@ import com.tangem.tap.common.analytics.events.DetailsScreen import com.tangem.tap.common.analytics.events.Token import com.tangem.tap.common.extensions.appendIfNotNull import com.tangem.tap.common.extensions.beginDelayedTransition -import com.tangem.tap.common.extensions.dispatchOnMain import com.tangem.tap.common.extensions.fitChipsByGroupWidth import com.tangem.tap.common.extensions.getColor import com.tangem.tap.common.extensions.getString @@ -39,7 +38,6 @@ import com.tangem.tap.common.extensions.show import com.tangem.tap.common.extensions.toQrCode import com.tangem.tap.common.recyclerView.SpaceItemDecoration import com.tangem.tap.common.redux.navigation.NavigationAction -import com.tangem.tap.domain.tokens.models.BlockchainNetwork import com.tangem.tap.features.wallet.models.Currency import com.tangem.tap.features.wallet.models.PendingTransaction import com.tangem.tap.features.wallet.models.PendingTransactionType @@ -65,6 +63,7 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import org.rekotlin.StoreSubscriber +import timber.log.Timber import javax.inject.Inject @Suppress("LargeClass", "MagicNumber") @@ -257,14 +256,7 @@ class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details), } } } else { - val blockchainNetwork = BlockchainNetwork( - blockchain = currency.blockchain, - derivationPath = currency.derivationPath, - tokens = emptyList(), - ) - - store.dispatchOnMain(WalletAction.LoadWallet(blockchainNetwork)) - store.dispatchOnMain(WalletAction.LoadFiatRate(coinsList = listOf(currency))) + Timber.e("Unable to refresh wallet details screen, no user wallet selected") } } } diff --git a/app/src/main/java/com/tangem/tap/proxy/DerivationManagerImpl.kt b/app/src/main/java/com/tangem/tap/proxy/DerivationManagerImpl.kt index 246d1ac726..ae2cdcfd1a 100644 --- a/app/src/main/java/com/tangem/tap/proxy/DerivationManagerImpl.kt +++ b/app/src/main/java/com/tangem/tap/proxy/DerivationManagerImpl.kt @@ -29,7 +29,7 @@ class DerivationManagerImpl( private val appStateHolder: AppStateHolder, ) : DerivationManager { - override suspend fun deriveMissingBlockchains(currency: Currency) = suspendCoroutine { continuation -> + override suspend fun deriveMissingBlockchains(currency: Currency) = suspendCoroutine { continuation -> val blockchain = Blockchain.fromNetworkId(currency.networkId) val card = appStateHolder.getActualCard() if (blockchain != null && card != null) { @@ -89,7 +89,7 @@ class DerivationManagerImpl( } scope.launch { - val selectedWallet = appStateHolder.userWalletsListManager?.selectedUserWalletSync + val selectedUserWallet = appStateHolder.userWalletsListManager?.selectedUserWalletSync val result = appStateHolder.tangemSdkManager?.derivePublicKeys( scanResponse.card.cardId, @@ -110,8 +110,8 @@ class DerivationManagerImpl( val updatedScanResponse = scanResponse.copy( derivedKeys = updatedDerivedKeys, ) - if (selectedWallet != null) { - val userWallet = selectedWallet.copy( + if (selectedUserWallet != null) { + val userWallet = selectedUserWallet.copy( scanResponse = updatedScanResponse, ) @@ -166,14 +166,10 @@ class DerivationManagerImpl( return DerivationData( derivations = mapKeyOfWalletPublicKey to toDerive, - alreadyDerivedKeys = alreadyDerivedKeys, - mapKeyOfWalletPublicKey = mapKeyOfWalletPublicKey, ) } private class DerivationData( val derivations: Pair>, - val alreadyDerivedKeys: ExtendedPublicKeysMap, - val mapKeyOfWalletPublicKey: ByteArrayKey, ) } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/proxy/UserWalletManagerImpl.kt b/app/src/main/java/com/tangem/tap/proxy/UserWalletManagerImpl.kt index d8273b39ee..fac9f927b3 100644 --- a/app/src/main/java/com/tangem/tap/proxy/UserWalletManagerImpl.kt +++ b/app/src/main/java/com/tangem/tap/proxy/UserWalletManagerImpl.kt @@ -2,13 +2,8 @@ package com.tangem.tap.proxy import com.tangem.blockchain.common.AmountType import com.tangem.blockchain.common.Blockchain -import com.tangem.blockchain.common.DerivationParams -import com.tangem.blockchain.common.DerivationStyle import com.tangem.blockchain.common.Token import com.tangem.blockchain.common.WalletManager -import com.tangem.blockchain.common.WalletManagerFactory -import com.tangem.domain.common.CardDTO -import com.tangem.domain.common.TapWorkarounds.derivationStyle import com.tangem.domain.common.extensions.fromNetworkId import com.tangem.domain.common.extensions.toCoinId import com.tangem.domain.common.extensions.toNetworkId @@ -19,20 +14,17 @@ import com.tangem.lib.crypto.models.Currency.NonNativeToken import com.tangem.lib.crypto.models.ProxyAmount import com.tangem.lib.crypto.models.ProxyFiatCurrency import com.tangem.tap.common.extensions.dispatchOnMain -import com.tangem.tap.domain.extensions.makeWalletManagerForApp import com.tangem.tap.domain.model.builders.UserWalletIdBuilder import com.tangem.tap.domain.tokens.models.BlockchainNetwork import com.tangem.tap.features.wallet.redux.WalletAction import com.tangem.tap.userWalletsListManager import com.tangem.tap.walletCurrenciesManager -import kotlinx.coroutines.delay -import org.rekotlin.Action +import timber.log.Timber import java.math.BigDecimal import com.tangem.tap.features.wallet.models.Currency as WalletCurrency class UserWalletManagerImpl( private val appStateHolder: AppStateHolder, - private val walletManagerFactory: WalletManagerFactory, ) : UserWalletManager { override suspend fun getUserTokens(networkId: String, isExcludeCustom: Boolean): List { @@ -117,13 +109,7 @@ class UserWalletManagerImpl( currenciesToAdd = listOf(currency.toWalletCurrency(blockchainNetwork)), ) } else { - val walletManager = getOrCreateBlockchain(blockchainNetwork, blockchain) - if (currency is NonNativeToken && !walletManager.cardTokens.contains(currency.toSdkToken())) { - val action = addNonNativeTokenToWalletAction(currency, card, blockchain) - val mainStore = requireNotNull(appStateHolder.mainStore) { "mainStore is null" } - mainStore.dispatchOnMain(action) - delay(DELAY_UPDATE_WALLET) - } + Timber.e("Unable to add token, selected user wallet is null") } } @@ -207,60 +193,11 @@ class UserWalletManagerImpl( ) } - private suspend fun getOrCreateBlockchain( - blockchainNetwork: BlockchainNetwork, - blockchain: Blockchain, - ): WalletManager { - val card = requireNotNull(appStateHolder.getActualCard()) { "card not found" } - val scanResponse = requireNotNull(appStateHolder.scanResponse) { "scanResponse not found" } - var walletManager = appStateHolder.walletState?.getWalletManager(blockchainNetwork) - if (walletManager == null) { - walletManager = walletManagerFactory.makeWalletManagerForApp( - scanResponse = scanResponse, - blockchain = blockchain, - derivationParams = createDerivationParams(card.derivationStyle), - ) - val action = WalletAction.MultiWallet.AddBlockchain( - blockchain = blockchainNetwork, - walletManager = walletManager, - save = true, - ) - val mainStore = requireNotNull(appStateHolder.mainStore) { "mainStore is null" } - mainStore.dispatchOnMain(action) - // workaround to wait until blockchain adds to walletStores and update appStateHolder.walletState - delay(DELAY_UPDATE_WALLET) - } - - return requireNotNull(walletManager) { "cant create walletManager" } - } - - private fun addNonNativeTokenToWalletAction(token: NonNativeToken, card: CardDTO, blockchain: Blockchain): Action { - return WalletAction.MultiWallet.AddToken( - token = Token( - id = token.id, - name = token.name, - symbol = token.symbol, - contractAddress = token.contractAddress, - decimals = token.decimalCount, - ), - blockchain = BlockchainNetwork( - blockchain, - card, - ), - save = true, - ) - } - override fun refreshWallet() { // workaround, should update wallet after transaction appStateHolder.mainStore?.dispatchOnMain(WalletAction.LoadData.Refresh) } - private fun createDerivationParams(derivationStyle: DerivationStyle?): DerivationParams? { - // todo clarify if its need to add Custom - return derivationStyle?.let { DerivationParams.Default(derivationStyle) } - } - private fun getActualWalletManager(blockchain: Blockchain): WalletManager { val card = requireNotNull(appStateHolder.getActualCard()) { "card not found" } val blockchainNetwork = BlockchainNetwork(blockchain, card) @@ -271,7 +208,6 @@ class UserWalletManagerImpl( companion object { private const val HEX_PREFIX = "0x" - private const val DELAY_UPDATE_WALLET = 500L } } diff --git a/app/src/main/java/com/tangem/tap/proxy/di/ProxyModule.kt b/app/src/main/java/com/tangem/tap/proxy/di/ProxyModule.kt index 1a65b1ddd0..a1fa2cd32a 100644 --- a/app/src/main/java/com/tangem/tap/proxy/di/ProxyModule.kt +++ b/app/src/main/java/com/tangem/tap/proxy/di/ProxyModule.kt @@ -1,6 +1,5 @@ package com.tangem.tap.proxy.di -import com.tangem.blockchain.common.WalletManagerFactory import com.tangem.lib.crypto.DerivationManager import com.tangem.lib.crypto.TransactionManager import com.tangem.lib.crypto.UserWalletManager @@ -29,7 +28,6 @@ class ProxyModule { fun provideUserWalletManager(appStateHolder: AppStateHolder): UserWalletManager { return UserWalletManagerImpl( appStateHolder = appStateHolder, - walletManagerFactory = WalletManagerFactory(), ) } From d28e324cd0d6b146eda8aed1062e032c0f110baa Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 28 Feb 2023 19:41:48 +0300 Subject: [PATCH 12/17] Updated on 2026-08-14 --- .../com/tangem/tap/common/extensions/Store.kt | 16 +- .../com/tangem/tap/domain/TapWalletManager.kt | 213 --------------- .../UserWalletsListManagerExtensions.kt | 6 + .../details/redux/DetailsMiddleware.kt | 5 +- .../features/onboarding/OnboardingHelper.kt | 21 +- .../redux/OnboardingOtherCardsMiddleware.kt | 12 +- .../redux/OnboardingWalletMiddleware.kt | 13 +- .../saveWallet/redux/SaveWalletMiddleware.kt | 8 +- .../send/redux/middlewares/SendMiddleware.kt | 22 +- .../features/tokens/redux/TokensMiddleware.kt | 41 ++- .../tap/features/wallet/redux/WalletAction.kt | 93 ------- .../tap/features/wallet/redux/WalletState.kt | 105 +------- .../middlewares/AppCurrencyMiddleware.kt | 12 +- .../middlewares/MultiWalletMiddleware.kt | 193 ++----------- .../redux/middlewares/WalletMiddleware.kt | 203 ++------------ .../redux/reducers/MultiWalletReducer.kt | 204 +------------- .../redux/reducers/OnWalletLoadedReducer.kt | 154 ----------- .../redux/reducers/TotalBalanceOperations.kt | 6 - .../wallet/redux/reducers/WalletReducer.kt | 255 ------------------ .../wallet/ui/WalletDetailsFragment.kt | 20 +- .../ui/wallet/saltPay/SaltPayWalletView.kt | 6 - .../tangem/tap/proxy/UserWalletManagerImpl.kt | 16 +- 22 files changed, 151 insertions(+), 1473 deletions(-) delete mode 100644 app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/OnWalletLoadedReducer.kt diff --git a/app/src/main/java/com/tangem/tap/common/extensions/Store.kt b/app/src/main/java/com/tangem/tap/common/extensions/Store.kt index 77ed58315d..bfd3fa99c2 100644 --- a/app/src/main/java/com/tangem/tap/common/extensions/Store.kt +++ b/app/src/main/java/com/tangem/tap/common/extensions/Store.kt @@ -1,6 +1,5 @@ package com.tangem.tap.common.extensions -import com.tangem.domain.common.ScanResponse import com.tangem.domain.common.extensions.withMainContext import com.tangem.tap.common.redux.AppState import com.tangem.tap.common.redux.StateDialog @@ -78,21 +77,18 @@ fun Store<*>.dispatchDialogHide() { /** * Dispatch action inside a coroutine with the Main dispatcher */ +@Deprecated( + message = "Use dispatchWithMain instead", + replaceWith = ReplaceWith(expression = "dispatchWithMain"), +) suspend fun dispatchOnMain(vararg actions: Action) { withMainContext { actions.forEach { store.dispatch(it) } } } -/** - * Dispatch action - */ -suspend fun Store<*>.onCardScanned(scanResponse: ScanResponse) { - store.state.globalState.tapWalletManager.onCardScanned(scanResponse) -} - fun Store<*>.dispatchOpenUrl(url: String) { - store.dispatch(NavigationAction.OpenUrl(url)) + dispatch(NavigationAction.OpenUrl(url)) } fun Store<*>.dispatchShare(url: String) { - store.dispatch(NavigationAction.Share(url)) + dispatch(NavigationAction.Share(url)) } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt b/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt index 4b34bbb8e7..aae069fed0 100644 --- a/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt @@ -1,109 +1,42 @@ package com.tangem.tap.domain -import com.tangem.blockchain.common.Amount import com.tangem.blockchain.common.Blockchain import com.tangem.blockchain.common.BlockchainSdkConfig import com.tangem.blockchain.common.Token import com.tangem.blockchain.common.Wallet -import com.tangem.blockchain.common.WalletManager import com.tangem.blockchain.common.WalletManagerFactory import com.tangem.common.doOnFailure import com.tangem.common.doOnSuccess -import com.tangem.common.services.Result import com.tangem.core.analytics.Analytics import com.tangem.datasource.config.ConfigManager -import com.tangem.domain.common.CardDTO import com.tangem.domain.common.ScanResponse -import com.tangem.domain.common.TapWorkarounds.isTestCard -import com.tangem.domain.common.ThrottlerWithValues import com.tangem.domain.common.extensions.withMainContext import com.tangem.operations.attestation.Attestation import com.tangem.tap.common.extensions.dispatchOnMain -import com.tangem.tap.common.extensions.safeUpdate import com.tangem.tap.common.extensions.setContext import com.tangem.tap.common.redux.global.GlobalAction -import com.tangem.tap.domain.extensions.makePrimaryWalletManager -import com.tangem.tap.domain.extensions.makeWalletManagersForApp import com.tangem.tap.domain.model.UserWallet -import com.tangem.tap.domain.tokens.models.BlockchainNetwork import com.tangem.tap.domain.walletStores.WalletStoresError -import com.tangem.tap.features.demo.isDemoCard import com.tangem.tap.features.details.redux.walletconnect.WalletConnectAction import com.tangem.tap.features.disclaimer.createDisclaimer import com.tangem.tap.features.disclaimer.redux.DisclaimerAction import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsAction -import com.tangem.tap.features.wallet.models.toBlockchainNetworks import com.tangem.tap.features.wallet.redux.WalletAction import com.tangem.tap.features.wallet.redux.middlewares.handleBasicAnalyticsEvent -import com.tangem.tap.network.NetworkConnectivity import com.tangem.tap.preferencesStorage import com.tangem.tap.store import com.tangem.tap.tangemSdkManager -import com.tangem.tap.userTokensRepository import com.tangem.tap.walletStoresManager -import com.tangem.utils.coroutines.AppCoroutineDispatcherProvider -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.withContext -import org.rekotlin.Action import timber.log.Timber class TapWalletManager { val walletManagerFactory: WalletManagerFactory by lazy { WalletManagerFactory(blockchainSdkConfig) } - // TODO("After adding DI") get dependencies by DI - val rates: RatesRepository by lazy { - RatesRepository( - tangemTechApi = store.state.domainNetworks.tangemTechService.api, - dispatchers = AppCoroutineDispatcherProvider(), - ) - } - private val blockchainSdkConfig by lazy { store.state.globalState.configManager?.config?.blockchainSdkConfig ?: BlockchainSdkConfig() } - private val walletManagersThrottler = - ThrottlerWithValues>(10000) - - suspend fun loadWalletData(walletManager: WalletManager) { - val blockchainNetwork = BlockchainNetwork.fromWalletManager(walletManager) - val result = if (walletManagersThrottler.isStillThrottled(blockchainNetwork)) { - walletManagersThrottler.geValue(blockchainNetwork)!! - } else { - val safeUpdateResult = walletManager.safeUpdate() - walletManagersThrottler.updateThrottlingTo(blockchainNetwork) - walletManagersThrottler.setValue(blockchainNetwork, safeUpdateResult) - safeUpdateResult - } - when (result) { - is Result.Success -> { - dispatchOnMain(WalletAction.LoadWallet.Success(result.data, blockchainNetwork)) - } - is Result.Failure -> { - when (result.error) { - is TapError.WalletManager.NoAccountError -> { - dispatchOnMain( - WalletAction.LoadWallet.NoAccount( - walletManager.wallet, - blockchainNetwork, - (result.error as TapError.WalletManager.NoAccountError).customMessage, - ), - ) - } - else -> { - dispatchOnMain( - WalletAction.LoadWallet.Failure( - walletManager.wallet, - result.error.localizedMessage, - ), - ) - } - } - } - } - } - suspend fun onWalletSelected(userWallet: UserWallet, refresh: Boolean) { Analytics.setContext(userWallet.scanResponse) val scanResponse = userWallet.scanResponse @@ -161,32 +94,6 @@ class TapWalletManager { } } - suspend fun onCardScanned(data: ScanResponse) { - walletManagersThrottler.clear() - store.state.globalState.feedbackManager?.infoHolder?.setCardInfo(data) - updateConfigManager(data) - - withMainContext { - store.dispatch(WalletAction.ResetState(data.card)) - store.dispatch(WalletConnectAction.ResetState) - store.dispatch(GlobalAction.SaveScanResponse(data)) - store.dispatch(WalletAction.SetIfTestnetCard(data.card.isTestCard)) - store.dispatch( - WalletAction.MultiWallet.SetIsMultiwalletAllowed( - data.cardTypesResolver.isMultiwalletAllowed(), - ), - ) - store.dispatch(WalletConnectAction.RestoreSessions(data)) - store.dispatch( - WalletAction.MultiWallet.ShowWalletBackupWarning( - show = data.card.settings.isBackupAllowed && - data.card.backupStatus == CardDTO.BackupStatus.NoBackup, - ), - ) - loadData(data) - } - } - fun updateConfigManager(data: ScanResponse) { val configManager = store.state.globalState.configManager val blockchain = data.cardTypesResolver.getBlockchain() @@ -203,126 +110,6 @@ class TapWalletManager { configManager?.resetToDefault(ConfigManager.IS_TOP_UP_ENABLED) } } - - suspend fun loadData(data: ScanResponse) { - dispatchOnMain(WalletAction.LoadCardInfo(data.card)) - getActionIfUnknownBlockchainOrEmptyWallet(data)?.let { - dispatchOnMain(it) - return - } - - if (data.cardTypesResolver.isMultiwalletAllowed()) { - dispatchOnMain(WalletAction.MultiWallet.ScheduleCheckForMissingDerivation) - loadMultiWalletData(data) - } else { - loadSingleWalletData(data) - } - - dispatchOnMain(WalletAction.LoadWallet()) - } - - private suspend fun loadMultiWalletData(scanResponse: ScanResponse) { - loadUserCurrencies(scanResponse, walletManagerFactory) - } - - private fun checkIfDerivationsAreMissing(blockchainNetworks: List, scanResponse: ScanResponse) { - blockchainNetworks.map { - if (it.tokens.isNotEmpty()) { - WalletAction.MultiWallet.AddTokens(it.tokens, it) - } - } - val missingDerivations = blockchainNetworks - .filter { - it.derivationPath != null && !scanResponse.hasDerivation(it.blockchain, it.derivationPath) - } - store.dispatch(WalletAction.MultiWallet.AddMissingDerivations(missingDerivations)) - } - - private suspend fun loadSingleWalletData(data: ScanResponse) { - val blockchain = data.cardTypesResolver.getBlockchain() - val primaryWalletManager = walletManagerFactory.makePrimaryWalletManager(data) - - if (blockchain != Blockchain.Unknown && primaryWalletManager != null) { - val blockchainNetwork = BlockchainNetwork.fromWalletManager(primaryWalletManager) - val actionsList = listOfNotNull( - WalletAction.MultiWallet.AddBlockchains( - blockchains = listOf(BlockchainNetwork.fromWalletManager(primaryWalletManager)), - walletManagers = listOf(primaryWalletManager), - ), - data.cardTypesResolver.getPrimaryToken()?.let { - primaryWalletManager.addToken(it) - primaryWalletManager.wallet.setAmount(Amount(it)) - WalletAction.MultiWallet.AddToken(it, blockchainNetwork, false) - }, - WalletAction.LoadFiatRate(), - ) - dispatchOnMain(*actionsList.toTypedArray()) - } - } - - private suspend fun loadUserCurrencies(scanResponse: ScanResponse, walletManagerFactory: WalletManagerFactory) { - val userTokens = userTokensRepository.getUserTokens(scanResponse.card) - withMainContext { - val blockchainNetworks = userTokens.toBlockchainNetworks() - val walletManagers = walletManagerFactory.makeWalletManagersForApp(scanResponse, userTokens) - store.dispatch( - WalletAction.MultiWallet.AddBlockchains( - blockchains = blockchainNetworks, - walletManagers = walletManagers, - ), - ) - - blockchainNetworks.filter { it.tokens.isNotEmpty() } - .map { - store.dispatch( - WalletAction.MultiWallet.AddTokens( - tokens = it.tokens, - blockchain = it, - ), - ) - } - checkIfDerivationsAreMissing(blockchainNetworks, scanResponse) - store.dispatch(WalletAction.LoadFiatRate(coinsList = userTokens)) - } - } - - suspend fun reloadData(data: ScanResponse) { - if (data.cardTypesResolver.isMultiwalletAllowed()) { - loadUserCurrencies(data, walletManagerFactory) - } - withContext(Dispatchers.Main) { - getActionIfUnknownBlockchainOrEmptyWallet(data)?.let { - store.dispatch(it) - return@withContext - } - if (!NetworkConnectivity.getInstance().isOnlineOrConnecting()) { - store.dispatch(WalletAction.LoadData.Failure(TapError.NoInternetConnection)) - return@withContext - } - - store.dispatch(WalletAction.LoadWallet()) - } - } - - private fun getActionIfUnknownBlockchainOrEmptyWallet(data: ScanResponse): WalletAction? { - return when { - // check order is important - data.cardTypesResolver.isTangemTwins() && !data.twinsIsTwinned() -> { - WalletAction.EmptyWallet - } - data.cardTypesResolver.getBlockchain() == Blockchain.Unknown && - !data.cardTypesResolver.isMultiwalletAllowed() -> { - WalletAction.LoadData.Failure(TapError.UnknownBlockchain) - } - data.isDemoCard() -> { - return null - } - data.card.wallets.isEmpty() -> { - WalletAction.EmptyWallet - } - else -> null - } - } } fun Wallet.getFirstToken(): Token? { diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/UserWalletsListManagerExtensions.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/UserWalletsListManagerExtensions.kt index 47f8b18a7c..6da40c3b93 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/UserWalletsListManagerExtensions.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/UserWalletsListManagerExtensions.kt @@ -5,6 +5,12 @@ import com.tangem.tap.domain.model.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 * 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 5609112906..89dc7ed09b 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 @@ -327,9 +327,10 @@ class DetailsMiddleware { scanResponse: ScanResponse?, enableAccessCodesSaving: Boolean, ): CompletionResult { - val userWallet = scanResponse?.let { UserWalletBuilder(it).build() } + val userWallet = userWalletsListManager.selectedUserWalletSync + ?: scanResponse?.let { UserWalletBuilder(it).build() } ?: return CompletionResult.Failure( - TangemSdkError.ExceptionError(IllegalStateException("scanResponse is null")), + error = TangemSdkError.ExceptionError(IllegalStateException("scanResponse is null")), ) updateUserWalletsListManager(enableUserWalletsSaving = true) 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 426d274352..fdf3a8b90b 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 @@ -68,6 +68,8 @@ object OnboardingHelper { when { // When should save user wallets, then save card without navigate to save wallet screen preferencesStorage.shouldSaveUserWallets -> scope.launch { + proceedWithScanResponse(scanResponse, backupCardsIds) + store.dispatchOnMain( SaveWalletAction.ProvideBackupInfo( scanResponse = scanResponse, @@ -81,7 +83,7 @@ object OnboardingHelper { // then open save wallet screen tangemSdkManager.canUseBiometry && preferencesStorage.shouldShowSaveUserWalletScreen -> scope.launch { - proceedWithScanResponse(scanResponse) + proceedWithScanResponse(scanResponse, backupCardsIds) delay(timeMillis = 1_200) @@ -96,7 +98,7 @@ object OnboardingHelper { } // If device has no biometry and save wallet screen has been shown, then go through old scenario else -> scope.launch { - proceedWithScanResponse(scanResponse) + proceedWithScanResponse(scanResponse, backupCardsIds) } } @@ -107,13 +109,16 @@ object OnboardingHelper { Analytics.removeContext() } - private suspend fun proceedWithScanResponse(scanResponse: ScanResponse) { - val userWallet = UserWalletBuilder(scanResponse).build().guard { - Timber.e("User wallet not created") - return - } + private suspend fun proceedWithScanResponse(scanResponse: ScanResponse, backupCardsIds: List?) { + val userWallet = UserWalletBuilder(scanResponse) + .backupCardsIds(backupCardsIds?.toSet()) + .build() + .guard { + Timber.e("User wallet not created") + return + } - userWalletsListManager.save(userWallet) + userWalletsListManager.save(userWallet, canOverride = true) .doOnFailure { error -> Timber.e(error, "Unable to save user wallet") } diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/otherCards/redux/OnboardingOtherCardsMiddleware.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/otherCards/redux/OnboardingOtherCardsMiddleware.kt index acbe7a146c..faa3dd2441 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/otherCards/redux/OnboardingOtherCardsMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/otherCards/redux/OnboardingOtherCardsMiddleware.kt @@ -11,10 +11,11 @@ import com.tangem.tap.common.redux.AppState import com.tangem.tap.common.redux.global.GlobalAction import com.tangem.tap.domain.tokens.models.BlockchainNetwork import com.tangem.tap.features.onboarding.OnboardingHelper -import com.tangem.tap.features.wallet.redux.WalletAction +import com.tangem.tap.features.wallet.models.toCurrencies import com.tangem.tap.scope import com.tangem.tap.store import com.tangem.tap.tangemSdkManager +import com.tangem.tap.userTokensRepository import kotlinx.coroutines.delay import kotlinx.coroutines.launch import org.rekotlin.Action @@ -112,9 +113,12 @@ private fun handleOtherCardsAction(action: Action) { ) } - store.dispatch( - WalletAction.MultiWallet.SaveCurrencies(blockchainNetworks, updatedResponse.card), - ) + scope.launch { + userTokensRepository.saveUserTokens( + card = result.data.card, + tokens = blockchainNetworks.toCurrencies(), + ) + } delay(DELAY_SDK_DIALOG_CLOSE) store.dispatch(OnboardingOtherCardsAction.SetStepOfScreen(OnboardingOtherCardsStep.Done)) diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt index 1ea9f428aa..4226d47b74 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt @@ -26,13 +26,14 @@ import com.tangem.tap.features.onboarding.OnboardingDialog import com.tangem.tap.features.onboarding.OnboardingHelper import com.tangem.tap.features.onboarding.products.wallet.saltPay.SaltPayActivationManagerFactory import com.tangem.tap.features.onboarding.products.wallet.saltPay.redux.OnboardingSaltPayAction +import com.tangem.tap.features.wallet.models.toCurrencies import com.tangem.tap.features.wallet.redux.Artwork -import com.tangem.tap.features.wallet.redux.WalletAction import com.tangem.tap.preferencesStorage import com.tangem.tap.scope import com.tangem.tap.store import com.tangem.tap.tangemSdk import com.tangem.tap.tangemSdkManager +import com.tangem.tap.userTokensRepository import kotlinx.coroutines.launch import org.rekotlin.Action import org.rekotlin.DispatchFunction @@ -135,12 +136,12 @@ private fun handleWalletAction(action: Action, state: () -> AppState?, dispatch: BlockchainNetwork(blockchain, result.data.card) } - store.dispatch( - WalletAction.MultiWallet.SaveCurrencies( - blockchainNetworks = blockchainNetworks, + scope.launch { + userTokensRepository.saveUserTokens( card = result.data.card, - ), - ) + tokens = blockchainNetworks.toCurrencies(), + ) + } startCardActivation(updatedResponse) store.dispatch(OnboardingWalletAction.ResumeBackup) } 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 921894565b..7c248c0f4f 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 @@ -19,6 +19,7 @@ import com.tangem.tap.common.redux.navigation.NavigationAction import com.tangem.tap.domain.model.builders.UserWalletBuilder import com.tangem.tap.domain.userWalletList.UserWalletsListManager import com.tangem.tap.domain.userWalletList.di.provideBiometricImplementation +import com.tangem.tap.domain.userWalletList.isLockable import com.tangem.tap.features.wallet.redux.WalletAction import com.tangem.tap.foregroundActivityObserver import com.tangem.tap.preferencesStorage @@ -97,7 +98,8 @@ internal class SaveWalletMiddleware { val userWallet = userWalletsListManager.selectedUserWalletSync ?: UserWalletBuilder(scanResponse) .backupCardsIds(state.backupInfo?.backupCardsIds) - .build() ?: return@launch + .build() + ?: return@launch provideBiometricUserWalletsListManager() @@ -110,13 +112,11 @@ internal class SaveWalletMiddleware { } .doOnSuccess { preferencesStorage.shouldSaveUserWallets = true - // Enable saving access codes only if this is the first time user save the wallet preferencesStorage.shouldSaveAccessCodes = isFirstSavedWallet || preferencesStorage.shouldSaveAccessCodes store.dispatchWithMain(SaveWalletAction.Save.Success) - store.dispatchWithMain(NavigationAction.PopBackTo(AppScreen.Wallet)) store.dispatchWithMain(WalletAction.UpdateCanSaveUserWallets(canSaveUserWallets = true)) } @@ -124,6 +124,8 @@ internal class SaveWalletMiddleware { } private suspend fun provideBiometricUserWalletsListManager() { + if (store.state.globalState.userWalletsListManager?.isLockable == true) return + val context = foregroundActivityObserver.foregroundActivity?.applicationContext.guard { val error = IllegalStateException("No activities in foreground") Timber.e(error) diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/SendMiddleware.kt b/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/SendMiddleware.kt index d4785af2f5..d86397139b 100644 --- a/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/SendMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/SendMiddleware.kt @@ -12,6 +12,7 @@ import com.tangem.blockchain.common.TransactionSender import com.tangem.blockchain.common.WalletManager import com.tangem.blockchain.extensions.SimpleResult import com.tangem.common.core.TangemSdkError +import com.tangem.common.extensions.guard import com.tangem.common.services.Result import com.tangem.core.analytics.Analytics import com.tangem.domain.common.CardDTO @@ -333,17 +334,16 @@ private fun updateWarnings(dispatch: (Action) -> Unit) { } private suspend fun updateWallet(walletManager: WalletManager) { - val selectedUserWallet = userWalletsListManager.selectedUserWalletSync - if (selectedUserWallet != null) { - val wallet = walletManager.wallet - walletCurrenciesManager.update( - userWallet = selectedUserWallet, - currency = Currency.Blockchain( - blockchain = wallet.blockchain, - derivationPath = wallet.publicKey.derivationPath?.rawPath, - ), - ) - } else { + val selectedUserWallet = userWalletsListManager.selectedUserWalletSync.guard { Timber.e("Unable to update wallet, no user wallet selected") + return } + val wallet = walletManager.wallet + walletCurrenciesManager.update( + userWallet = selectedUserWallet, + currency = Currency.Blockchain( + blockchain = wallet.blockchain, + derivationPath = wallet.publicKey.derivationPath?.rawPath, + ), + ) } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensMiddleware.kt b/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensMiddleware.kt index 7d9b1027ed..0896404d09 100644 --- a/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensMiddleware.kt @@ -5,6 +5,7 @@ import com.tangem.blockchain.common.DerivationStyle import com.tangem.common.CompletionResult import com.tangem.common.card.EllipticCurve import com.tangem.common.extensions.ByteArrayKey +import com.tangem.common.extensions.guard import com.tangem.common.extensions.toMapKey import com.tangem.common.flatMap import com.tangem.common.hdWallet.DerivationPath @@ -273,35 +274,33 @@ class TokensMiddleware { ) private fun submitAdd(scanResponse: ScanResponse, currencyList: List) { - val selectedUserWallet = userWalletsListManager.selectedUserWalletSync - if (selectedUserWallet != null) { - scope.launch { - userWalletsListManager.update( - userWalletId = selectedUserWallet.walletId, - update = { userWallet -> - userWallet.copy(scanResponse = scanResponse) - }, - ) - .flatMap { updatedUserWallet -> - walletCurrenciesManager.addCurrencies( - userWallet = updatedUserWallet, - currenciesToAdd = currencyList, - ) - } - } - } else { + val selectedUserWallet = userWalletsListManager.selectedUserWalletSync.guard { Timber.e("Unable to add currencies, no user wallet selected") + return + } + scope.launch { + userWalletsListManager.update( + userWalletId = selectedUserWallet.walletId, + update = { userWallet -> + userWallet.copy(scanResponse = scanResponse) + }, + ) + .flatMap { updatedUserWallet -> + walletCurrenciesManager.addCurrencies( + userWallet = updatedUserWallet, + currenciesToAdd = currencyList, + ) + } } } private suspend fun removeCurrenciesIfNeeded(currencies: List) { if (currencies.isEmpty()) return - val selectedUserWallet = userWalletsListManager.selectedUserWalletSync - if (selectedUserWallet != null) { - walletCurrenciesManager.removeCurrencies(selectedUserWallet, currencies) - } else { + val selectedUserWallet = userWalletsListManager.selectedUserWalletSync.guard { Timber.e("Unable to remove currencies, no user wallet selected") + return } + walletCurrenciesManager.removeCurrencies(selectedUserWallet, currencies) } private fun isNeedToDerive(scanResponse: ScanResponse, currency: Currency): Boolean { diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletAction.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletAction.kt index a305989a17..390b337f30 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletAction.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletAction.kt @@ -2,12 +2,8 @@ package com.tangem.tap.features.wallet.redux import android.content.Context import com.tangem.blockchain.common.Amount -import com.tangem.blockchain.common.Token -import com.tangem.blockchain.common.Wallet -import com.tangem.blockchain.common.WalletManager import com.tangem.blockchain.common.address.AddressType import com.tangem.core.analytics.AnalyticsEvent -import com.tangem.domain.common.CardDTO import com.tangem.tap.common.entities.FiatCurrency import com.tangem.tap.common.redux.NotificationAction import com.tangem.tap.domain.TapError @@ -20,7 +16,6 @@ import com.tangem.tap.features.wallet.models.TotalBalance import com.tangem.tap.features.wallet.redux.models.WalletDialog import com.tangem.wallet.R import org.rekotlin.Action -import java.math.BigDecimal sealed class WalletAction : Action { @@ -28,81 +23,20 @@ sealed class WalletAction : Action { data class UpdateCanSaveUserWallets(val canSaveUserWallets: Boolean) : WalletAction() - data class ResetState(val newCard: CardDTO) : WalletAction() - - data class SetIfTestnetCard(val isTestnet: Boolean) : WalletAction() - object LoadData : WalletAction() { object Refresh : WalletAction() object Success : WalletAction() data class Failure(val error: TapError?) : WalletAction() } - data class LoadWallet( - val blockchain: BlockchainNetwork? = null, - val walletManager: WalletManager? = null, - ) : WalletAction() { - data class Success(val wallet: Wallet, val blockchain: BlockchainNetwork) : WalletAction() - data class NoAccount( - val wallet: Wallet, - val blockchain: BlockchainNetwork, - val amountToCreateAccount: String, - ) : WalletAction() - - data class Failure(val wallet: Wallet, val errorMessage: String? = null) : WalletAction() - } - - data class SetArtworkId(val artworkId: String?) : WalletAction() - - sealed class UserTokens : WalletAction() { - object Loading : UserTokens() - object Loaded : UserTokens() - } - sealed class MultiWallet : WalletAction() { - data class SetIsMultiwalletAllowed(val isMultiwalletAllowed: Boolean) : MultiWallet() - - data class AddBlockchains( - val blockchains: List, - val walletManagers: List, - ) : MultiWallet() - - data class AddTokens( - val tokens: List, - val blockchain: BlockchainNetwork, - ) : MultiWallet() - - data class AddBlockchain( - val blockchain: BlockchainNetwork, - val walletManager: WalletManager?, - val save: Boolean, - ) : MultiWallet() - - data class AddToken( - val token: Token, - val blockchain: BlockchainNetwork, - val save: Boolean, - ) : MultiWallet() - - data class SaveCurrencies( - val blockchainNetworks: List, - val card: CardDTO? = null, - ) : MultiWallet() - - data class TokenLoaded( - val amount: Amount, - val token: Token, - val blockchain: BlockchainNetwork, - ) : MultiWallet() data class SelectWallet(val currency: Currency?) : MultiWallet() data class SetSingleWalletCurrency(val currency: Currency?) : MultiWallet() data class TryToRemoveWallet(val currency: Currency) : MultiWallet() data class RemoveWallet(val currency: Currency) : MultiWallet() - data class RemoveWallets(val currencies: List) : MultiWallet() - data class ShowWalletBackupWarning(val show: Boolean) : MultiWallet() object BackupWallet : MultiWallet() object ScheduleCheckForMissingDerivation : MultiWallet() data class AddMissingDerivations(val blockchains: List) : MultiWallet() @@ -129,24 +63,6 @@ sealed class WalletAction : Action { class CheckRemainingSignatures(val remainingSignatures: Int?) : Warnings() } - data class LoadFiatRate( - val wallet: Wallet? = null, - val coinsList: List? = null, - ) : WalletAction() { - data class Success( - val fiatRates: Map, - ) : WalletAction() - - object Failure : WalletAction() - } - - class LoadCardInfo(val card: CardDTO) : WalletAction() - - data class LoadArtwork(val card: CardDTO, val artworkId: String?) : WalletAction() { - data class Success(val artwork: Artwork) : WalletAction() - object Failure : WalletAction() - } - data class Scan(val onScanSuccessEvent: AnalyticsEvent?) : WalletAction() data class Send(val amount: Amount? = null) : WalletAction() @@ -183,7 +99,6 @@ sealed class WalletAction : Action { data class ExploreAddress(val exploreUrl: String, val context: Context) : WalletAction() object CreateWallet : WalletAction() - object EmptyWallet : WalletAction() object ChangeWallet : WalletAction() object ShowSaveWalletIfNeeded : WalletAction() @@ -206,14 +121,6 @@ sealed class WalletAction : Action { data class ChangeSelectedAddress(val type: AddressType) : WalletAction() - data class SetWalletRent( - val wallet: Wallet, - val minRent: String, - val rentExempt: String, - ) : WalletAction() - - data class RemoveWalletRent(val wallet: Wallet) : WalletAction() - sealed class AppCurrencyAction : WalletAction() { object ChooseAppCurrency : AppCurrencyAction() data class SelectAppCurrency(val fiatCurrency: FiatCurrency) : AppCurrencyAction() diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletState.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletState.kt index 23c86b2a7e..81b306448b 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletState.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletState.kt @@ -2,8 +2,6 @@ package com.tangem.tap.features.wallet.redux import android.graphics.Bitmap import com.tangem.blockchain.common.Blockchain -import com.tangem.blockchain.common.Token -import com.tangem.blockchain.common.Wallet import com.tangem.blockchain.common.WalletManager import com.tangem.blockchain.common.address.AddressType import com.tangem.tap.common.entities.Button @@ -11,16 +9,13 @@ import com.tangem.tap.common.extensions.toQrCode import com.tangem.tap.common.redux.global.CryptoCurrencyName import com.tangem.tap.common.toggleWidget.WidgetState import com.tangem.tap.domain.configurable.warningMessage.WarningMessage -import com.tangem.tap.domain.getFirstToken import com.tangem.tap.domain.tokens.models.BlockchainNetwork import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsState import com.tangem.tap.features.wallet.models.Currency import com.tangem.tap.features.wallet.models.TotalBalance -import com.tangem.tap.features.wallet.redux.reducers.calculateTotalFiatAmount import com.tangem.tap.features.wallet.redux.reducers.findProgressState import com.tangem.tap.features.wallet.ui.BalanceStatus import com.tangem.tap.store -import com.tangem.tap.userWalletsListManager import org.rekotlin.StateType import kotlin.properties.ReadOnlyProperty @@ -88,12 +83,6 @@ data class WalletState( val primaryWalletData: WalletData? get() = primaryWalletStore?.walletsData?.firstOrNull() - val primaryBlockchain: Blockchain? - get() = primaryWalletManager?.wallet?.blockchain - - val primaryToken: Token? - get() = primaryWalletManager?.wallet?.getFirstToken() - val primaryTokenData: WalletData? get() = primaryWalletStore?.walletsData?.toMutableList() ?.apply { remove(primaryWalletData) } @@ -103,9 +92,6 @@ data class WalletState( primaryWalletData?.currencyData?.status != BalanceStatus.EmptyCard && primaryWalletData?.currencyData?.status != BalanceStatus.UnknownBlockchain - val hasSavedWallets: Boolean - get() = userWalletsListManager.hasUserWallets - fun getWalletManager(currency: Currency?): WalletManager? { if (currency?.blockchain == null) return null return getWalletStore(currency)?.walletManager @@ -132,14 +118,7 @@ data class WalletState( } } - fun getWalletStore(wallet: Wallet?): WalletStore? { - if (wallet == null) return null - val currency = - Currency.Blockchain(wallet.blockchain, wallet.publicKey.derivationPath?.rawPath) - return getWalletStore(currency) - } - - fun getWalletStore(blockchainNetwork: BlockchainNetwork?): WalletStore? { + private fun getWalletStore(blockchainNetwork: BlockchainNetwork?): WalletStore? { if (blockchainNetwork == null) return null return walletsStores.firstOrNull { it.blockchainNetwork.derivationPath == blockchainNetwork.derivationPath && @@ -152,26 +131,12 @@ data class WalletState( return getWalletStore(currency)?.walletsData?.firstOrNull { it.currency == currency } } - fun replaceWalletStoreInWalletsStores(wallet: WalletStore?): List { - if (wallet == null) return walletsStores - var changed = false - val updatedWallets = walletsStores.map { - if (it.blockchainNetwork == wallet.blockchainNetwork) { - changed = true - wallet - } else { - it - } - } - return if (changed) updatedWallets else walletsStores + wallet - } - fun updateWalletData(walletData: WalletData?): WalletState { if (walletData == null) return this return updateWalletsData(listOf(walletData)) } - fun updateWalletsData(walletsData: List): WalletState { + private fun updateWalletsData(walletsData: List): WalletState { val walletStores = walletsData .map { BlockchainNetwork(it.currency.blockchain, it.currency.derivationPath, emptyList()) } .distinct().map { getWalletStore(it) }.mapNotNull { it?.updateWallets(walletsData) } @@ -179,12 +144,6 @@ data class WalletState( return updateWalletsStores(walletStores) } - fun updateWalletStore(walletStore: WalletStore?): WalletState { - return copy(walletsStores = replaceWalletStoreInWalletsStores(walletStore)) - .updateTotalBalance() - .updateProgressState() - } - private fun updateWalletsStores(walletStores: List): WalletState { val walletStoresMutable = walletStores.toMutableList() val updatedWallets = walletsStores.map { oldWalletStore -> @@ -199,54 +158,9 @@ data class WalletState( } } return copy(walletsStores = updatedWallets + walletStoresMutable) - .updateTotalBalance() .updateProgressState() } - fun removeWalletData(walletData: WalletData?): WalletState { - if (walletData == null) return this - return when (val currency = walletData.currency) { - is Currency.Blockchain -> { - val walletStores = walletsStores.filterNot { - it.blockchainNetwork.blockchain == currency.blockchain && - it.blockchainNetwork.derivationPath == currency.derivationPath - } - copy(walletsStores = walletStores) - .updateTotalBalance() - .updateProgressState() - } - is Currency.Token -> { - val walletStore = getWalletStore(walletData.currency) - val walletDataList = walletStore?.walletsData - ?.filterNot { it.currency == walletData.currency } - ?: emptyList() - val updatedWalletManager = walletStore?.walletManager?.also { it.removeToken(currency.token) } - val updatedWalletStore = walletStore?.copy( - walletsData = walletDataList, - walletManager = updatedWalletManager, - ) - updateWalletStore(updatedWalletStore) - } - } - } - - private fun updateTotalBalance(): WalletState { - val walletsData = this.walletsStores - .flatMap(WalletStore::walletsData) - - return if (walletsData.isNotEmpty()) { - this.copy( - totalBalance = TotalBalance( - state = walletsData.findProgressState(), - fiatAmount = walletsData.calculateTotalFiatAmount(), - fiatCurrency = store.state.globalState.appCurrency, - ), - ) - } else { - this.copy(totalBalance = null) - } - } - private fun updateProgressState(): WalletState { val walletsData = this.walletsStores .flatMap(WalletStore::walletsData) @@ -268,21 +182,6 @@ data class WalletState( } } -fun List.replaceSomeWalletsData(newWallets: List): List { - val remainingWallets: MutableList = newWallets.toMutableList() - val updatedWallets = this.map { wallet -> - val newWallet = newWallets - .firstOrNull { wallet.currency == it.currency } - if (newWallet == null) { - wallet - } else { - remainingWallets.remove(newWallet) - newWallet - } - } - return updatedWallets + remainingWallets -} - enum class ProgressState : WidgetState { Loading, Refreshing, Done, Error } enum class ErrorType { NoInternetConnection } diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/AppCurrencyMiddleware.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/AppCurrencyMiddleware.kt index ad7e1d4914..974ef88df8 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/AppCurrencyMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/AppCurrencyMiddleware.kt @@ -1,5 +1,6 @@ package com.tangem.tap.features.wallet.redux.middlewares +import com.tangem.common.extensions.guard import com.tangem.core.analytics.Analytics import com.tangem.datasource.api.tangemTech.models.CurrenciesResponse import com.tangem.tap.common.analytics.events.AnalyticsParam @@ -67,13 +68,12 @@ class AppCurrencyMiddleware( store.dispatch(GlobalAction.ChangeAppCurrency(action.fiatCurrency)) store.dispatch(DetailsAction.ChangeAppCurrency(action.fiatCurrency)) store.dispatch(WalletSelectorAction.ChangeAppCurrency(action.fiatCurrency)) - val selectedUserWallet = userWalletsListManager.selectedUserWalletSync - if (selectedUserWallet != null) { - scope.launch { - tapWalletManager.loadData(selectedUserWallet, refresh = true) - } - } else { + val selectedUserWallet = userWalletsListManager.selectedUserWalletSync.guard { Timber.e("Unable to select currency, no user wallet selected") + return + } + scope.launch { + tapWalletManager.loadData(selectedUserWallet, refresh = true) } } diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/MultiWalletMiddleware.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/MultiWalletMiddleware.kt index 65a19b3d79..2642cbeb50 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/MultiWalletMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/MultiWalletMiddleware.kt @@ -1,8 +1,5 @@ package com.tangem.tap.features.wallet.redux.middlewares -import com.tangem.blockchain.common.AmountType -import com.tangem.blockchain.common.Token -import com.tangem.blockchain.common.WalletManager import com.tangem.common.doOnSuccess import com.tangem.common.extensions.guard import com.tangem.common.flatMap @@ -13,23 +10,14 @@ import com.tangem.tap.common.extensions.addContext import com.tangem.tap.common.extensions.dispatchDialogShow import com.tangem.tap.common.extensions.dispatchErrorNotification import com.tangem.tap.common.extensions.dispatchOnMain -import com.tangem.tap.common.extensions.safeUpdate import com.tangem.tap.common.redux.global.GlobalAction -import com.tangem.tap.common.redux.global.GlobalState import com.tangem.tap.common.redux.navigation.AppScreen import com.tangem.tap.common.redux.navigation.NavigationAction import com.tangem.tap.domain.TapError -import com.tangem.tap.domain.extensions.makeWalletManagerForApp import com.tangem.tap.domain.model.UserWallet -import com.tangem.tap.domain.tokens.models.BlockchainNetwork -import com.tangem.tap.features.demo.DemoHelper -import com.tangem.tap.features.demo.isDemoCard -import com.tangem.tap.features.wallet.models.Currency -import com.tangem.tap.features.wallet.models.toCurrencies import com.tangem.tap.features.wallet.redux.WalletAction import com.tangem.tap.features.wallet.redux.WalletState import com.tangem.tap.features.wallet.redux.models.WalletDialog -import com.tangem.tap.features.wallet.redux.reducers.toWallet import com.tangem.tap.scope import com.tangem.tap.store import com.tangem.tap.tangemSdkManager @@ -39,69 +27,19 @@ import com.tangem.tap.walletCurrenciesManager import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import timber.log.Timber -import java.math.BigDecimal class MultiWalletMiddleware { @Suppress("LongMethod", "ComplexMethod") fun handle( action: WalletAction.MultiWallet, walletState: WalletState?, - globalState: GlobalState?, ) { - val globalState = globalState ?: return - when (action) { - is WalletAction.MultiWallet.AddBlockchains -> { - handleAddingWalletManagers(globalState, action.walletManagers) - } is WalletAction.MultiWallet.SelectWallet -> { if (action.currency != null) { store.dispatch(NavigationAction.NavigateTo(AppScreen.WalletDetails)) } } - is WalletAction.MultiWallet.AddToken -> { - addTokens(listOf(action.token), action.blockchain, walletState, globalState, action.save) - } - is WalletAction.MultiWallet.AddTokens -> { - addTokens(action.tokens, action.blockchain, walletState, globalState, save = false) - } - is WalletAction.MultiWallet.AddBlockchain -> { - action.walletManager?.let { - handleAddingWalletManagers(globalState, listOf(action.walletManager)) - } - val currencies: List = - (walletState?.currencies ?: emptyList()) + action.blockchain.toCurrencies() - - if (action.save && globalState.scanResponse != null) { - scope.launch { - userTokensRepository.saveUserTokens( - card = globalState.scanResponse.card, - tokens = currencies, - ) - } - } - - store.dispatch( - WalletAction.LoadFiatRate( - coinsList = listOf( - Currency.Blockchain( - action.blockchain.blockchain, - action.blockchain.derivationPath, - ), - ), - ), - ) - store.dispatch( - WalletAction.LoadWallet( - blockchain = action.blockchain, - walletManager = action.walletManager, - ), - ) - } - is WalletAction.MultiWallet.SaveCurrencies -> { - val card = action.card ?: globalState.scanResponse?.card ?: return - scope.launch { userTokensRepository.saveUserTokens(card, action.blockchainNetworks.toCurrencies()) } - } is WalletAction.MultiWallet.TryToRemoveWallet -> { val currency = action.currency val walletManager = walletState?.getWalletManager(currency).guard { @@ -131,47 +69,36 @@ class MultiWalletMiddleware { } } is WalletAction.MultiWallet.RemoveWallet -> { - val selectedUserWallet = userWalletsListManager.selectedUserWalletSync - if (selectedUserWallet != null) { - scope.launch { - walletCurrenciesManager.removeCurrency( - userWallet = selectedUserWallet, - currencyToRemove = action.currency, - ) - } - } else { + val selectedUserWallet = userWalletsListManager.selectedUserWalletSync.guard { Timber.e("Unable to remove wallet, no user wallet selected") - } - } - is WalletAction.MultiWallet.RemoveWallets -> { - val card = globalState.scanResponse?.card.guard { - store.dispatchErrorNotification(TapError.UnsupportedState("card is NULL")) - store.dispatch(NavigationAction.PopBackTo(AppScreen.Home)) return } - var currencies = walletState?.currencies ?: emptyList() - currencies = currencies.filterNot { action.currencies.contains(it) } - scope.launch { userTokensRepository.saveUserTokens(card, currencies) } - store.dispatch(WalletAction.MultiWallet.SelectWallet(null)) - } - is WalletAction.MultiWallet.ShowWalletBackupWarning -> Unit - is WalletAction.MultiWallet.BackupWallet -> { - store.state.globalState.scanResponse?.let { - Analytics.addContext(it) - store.dispatch(GlobalAction.Onboarding.Start(it, canSkipBackup = false)) - store.dispatch(NavigationAction.NavigateTo(AppScreen.OnboardingWallet)) + scope.launch { + walletCurrenciesManager.removeCurrency( + userWallet = selectedUserWallet, + currencyToRemove = action.currency, + ) } } + is WalletAction.MultiWallet.BackupWallet -> { + val selectedUserWallet = userWalletsListManager.selectedUserWalletSync.guard { + Timber.e("Unable to backup wallet, no user wallet selected") + return + } + val scanResponse = selectedUserWallet.scanResponse + Analytics.addContext(scanResponse) + store.dispatch(GlobalAction.Onboarding.Start(scanResponse, canSkipBackup = false)) + store.dispatch(NavigationAction.NavigateTo(AppScreen.OnboardingWallet)) + } is WalletAction.MultiWallet.AddMissingDerivations -> { scope.launch { handleBasicAnalyticsEvent() } } is WalletAction.MultiWallet.ScanToGetDerivations -> { - val selectedUserWallet = userWalletsListManager.selectedUserWalletSync - if (selectedUserWallet != null) { - scanAndUpdateCard(selectedUserWallet, walletState) - } else { + val selectedUserWallet = userWalletsListManager.selectedUserWalletSync.guard { Timber.e("Unable to scan to get derivations, no user wallet selected") + return } + scanAndUpdateCard(selectedUserWallet, walletState) } else -> {} } @@ -203,86 +130,4 @@ class MultiWalletMiddleware { store.state.globalState.tapWalletManager.loadData(updatedUserWallet, refresh = true) } } - - private fun addDummyBalances(walletManagers: List) { - walletManagers.forEach { - if (it.wallet.fundsAvailable(AmountType.Coin) == BigDecimal.ZERO) { - DemoHelper.injectDemoBalance(it) - } - } - } - - private fun handleAddingWalletManagers( - globalState: GlobalState, - walletManagers: List, - ) { - globalState.feedbackManager?.infoHolder?.setWalletsInfo(walletManagers) - if (globalState.scanResponse?.isDemoCard() == true) { - addDummyBalances(walletManagers) - } - } - - private fun addTokens( - tokens: List, - blockchainNetwork: BlockchainNetwork, - walletState: WalletState?, - globalState: GlobalState?, - save: Boolean, - ) { - if (tokens.isEmpty()) return - val scanResponse = globalState?.scanResponse ?: return - val wmFactory = globalState.tapWalletManager.walletManagerFactory - val walletState = walletState ?: return - val walletManager = walletState.getWalletManager(blockchainNetwork)?.also { - if (save) { - val wallets = tokens.mapNotNull { token -> token.toWallet(walletState, blockchainNetwork) } - val currencies = walletState.updateWalletsData(wallets).currencies - scope.launch { userTokensRepository.saveUserTokens(scanResponse.card, currencies) } - } - } ?: wmFactory.makeWalletManagerForApp(scanResponse, blockchainNetwork)?.also { - store.dispatchOnMain( - WalletAction.MultiWallet.AddBlockchain( - blockchain = blockchainNetwork.updateTokens(tokens), - walletManager = it, - save = save, - ), - ) - } - - store.dispatchOnMain( - WalletAction.LoadFiatRate( - coinsList = tokens.map { token -> - Currency.Token( - token, - blockchainNetwork.blockchain, - blockchainNetwork.derivationPath, - ) - }, - ), - ) - if (tokens.isNotEmpty()) walletManager?.addTokens(tokens) - - scope.launch { - when (val result = walletManager?.safeUpdate()) { - is com.tangem.common.services.Result.Success -> { - val wallet = result.data - wallet.getTokens() - .filter { tokens.contains(it) } - .mapNotNull { token -> - wallet.getTokenAmount(token)?.let { amount -> Pair(token, amount) } - } - .forEach { (token, tokenAmount) -> - store.dispatchOnMain( - WalletAction.MultiWallet.TokenLoaded( - amount = tokenAmount, - token = token, - blockchain = blockchainNetwork, - ), - ) - } - } - else -> Unit - } - } - } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt index 0ba437fdb6..276265fa9e 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt @@ -1,17 +1,11 @@ package com.tangem.tap.features.wallet.redux.middlewares import com.google.firebase.crashlytics.FirebaseCrashlytics -import com.tangem.blockchain.blockchains.solana.RentProvider import com.tangem.blockchain.common.Amount import com.tangem.blockchain.common.AmountType -import com.tangem.blockchain.common.WalletManager import com.tangem.common.CompletionResult -import com.tangem.common.extensions.isZero -import com.tangem.common.services.Result +import com.tangem.common.extensions.guard import com.tangem.core.analytics.Analytics -import com.tangem.domain.common.extensions.withMainContext -import com.tangem.operations.attestation.Attestation -import com.tangem.operations.attestation.OnlineCardVerifier import com.tangem.tap.common.analytics.converters.BasicEventsPreChecker import com.tangem.tap.common.analytics.converters.BasicEventsSourceData import com.tangem.tap.common.analytics.events.AnalyticsParam @@ -19,22 +13,16 @@ import com.tangem.tap.common.analytics.events.Basic import com.tangem.tap.common.analytics.events.MainScreen import com.tangem.tap.common.analytics.events.Token import com.tangem.tap.common.extensions.copyToClipboard -import com.tangem.tap.common.extensions.dispatchDebugErrorNotification import com.tangem.tap.common.extensions.dispatchErrorNotification import com.tangem.tap.common.extensions.dispatchOnMain import com.tangem.tap.common.extensions.dispatchOpenUrl import com.tangem.tap.common.extensions.dispatchToastNotification import com.tangem.tap.common.extensions.isGreaterThan -import com.tangem.tap.common.extensions.onCardScanned import com.tangem.tap.common.extensions.shareText -import com.tangem.tap.common.extensions.stripZeroPlainString import com.tangem.tap.common.redux.AppState -import com.tangem.tap.common.redux.global.GlobalAction import com.tangem.tap.common.redux.navigation.AppScreen import com.tangem.tap.common.redux.navigation.NavigationAction import com.tangem.tap.domain.TapError -import com.tangem.tap.domain.failedRates -import com.tangem.tap.domain.loadedRates import com.tangem.tap.domain.model.WalletDataModel import com.tangem.tap.domain.model.WalletStoreModel import com.tangem.tap.domain.model.builders.UserWalletIdBuilder @@ -43,9 +31,6 @@ import com.tangem.tap.features.demo.DemoHelper import com.tangem.tap.features.home.redux.HomeAction import com.tangem.tap.features.send.redux.PrepareSendScreen import com.tangem.tap.features.wallet.models.Currency -import com.tangem.tap.features.wallet.models.PendingTransactionType -import com.tangem.tap.features.wallet.models.filterByCoin -import com.tangem.tap.features.wallet.models.getPendingTransactions import com.tangem.tap.features.wallet.models.getSendableAmounts import com.tangem.tap.features.wallet.redux.WalletAction import com.tangem.tap.features.wallet.redux.WalletData @@ -64,8 +49,6 @@ import com.tangem.tap.userWalletsListManagerSafe import com.tangem.tap.walletStoresManager import com.tangem.wallet.R import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.async -import kotlinx.coroutines.awaitAll import kotlinx.coroutines.delay import kotlinx.coroutines.launch import org.rekotlin.Action @@ -108,97 +91,26 @@ class WalletMiddleware { when (action) { is WalletAction.TradeCryptoAction -> tradeCryptoMiddleware.handle(state, action) is WalletAction.Warnings -> warningsMiddleware.handle(action, globalState) - is WalletAction.MultiWallet -> multiWalletMiddleware.handle(action, walletState, globalState) + is WalletAction.MultiWallet -> multiWalletMiddleware.handle(action, walletState) is WalletAction.AppCurrencyAction -> appCurrencyMiddleware.handle(action) is WalletAction.DialogAction -> walletDialogMiddleware.handle(action) - is WalletAction.LoadWallet -> { - scope.launch { - if (action.blockchain == null) { - walletState.walletManagers.map { walletManager -> - async { globalState.tapWalletManager.loadWalletData(walletManager) } - }.awaitAll() - handleBasicAnalyticsEvent() - } else { - val walletManager = walletState.getWalletManager(action.blockchain) - ?: action.walletManager - walletManager?.let { globalState.tapWalletManager.loadWalletData(it) } - } - } - } - is WalletAction.LoadWallet.Success -> { - checkForRentWarning(walletState.getWalletManager(action.blockchain)) - val coinAmount = action.wallet.amounts[AmountType.Coin]?.value - if (coinAmount?.isZero() == false && walletState.getWalletData(action.blockchain) == null) { - store.dispatch( - WalletAction.MultiWallet.AddBlockchain( - blockchain = action.blockchain, - walletManager = null, - save = true, - ), - ) - store.dispatch(WalletAction.LoadWallet.Success(action.wallet, action.blockchain)) - } - store.dispatch(WalletAction.Warnings.CheckHashesCount.CheckHashesCountOnline) - warningsMiddleware.tryToShowAppRatingWarning(action.wallet) - } - is WalletAction.LoadFiatRate -> { - val appCurrencyId = globalState.appCurrency.code - scope.launch { - val coinsList = when { - action.wallet != null -> { - val wallet = action.wallet - wallet.getTokens() - .map { Currency.Token(it, wallet.blockchain, wallet.publicKey.derivationPath?.rawPath) } - .plus(Currency.Blockchain(wallet.blockchain, wallet.publicKey.derivationPath?.rawPath)) - } - action.coinsList != null -> action.coinsList - else -> { - if (walletState.isMultiwalletAllowed) { - walletState.walletsDataFromStores.map { it.currency } - } else { - val derivationPath = walletState.primaryWalletData?.currency?.derivationPath - val primaryBlockchain = walletState.primaryBlockchain - val primaryToken = walletState.primaryToken - listOfNotNull( - primaryBlockchain?.let { Currency.Blockchain(it, derivationPath) }, - primaryToken?.let { Currency.Token(it, primaryBlockchain!!, derivationPath) }, - ) - } - } - } - val ratesResult = globalState.tapWalletManager.rates.loadFiatRate( - currencyId = appCurrencyId, - coinsList = coinsList, - ) - when (ratesResult) { - is Result.Success -> { - ratesResult.data.loadedRates.let { - dispatchOnMain(WalletAction.LoadFiatRate.Success(it)) - } - ratesResult.data.failedRates.forEach { (currency, throwable) -> - Timber.e( - throwable, - "Loading rates failed for [%s]", - currency.currencySymbol, - ) - } - } - is Result.Failure -> { - store.dispatchDebugErrorNotification("LoadFiatRate.Failure") - dispatchOnMain(WalletAction.LoadFiatRate.Failure) - } - } - } - } is WalletAction.CreateWallet -> { scope.launch { - val result = tangemSdkManager.createWallet(globalState.scanResponse?.card?.cardId) - when (result) { + when (val result = tangemSdkManager.createWallet(globalState.scanResponse?.card?.cardId)) { is CompletionResult.Success -> { - val scanResponse = globalState.scanResponse?.copy(card = result.data) - scanResponse?.let { store.onCardScanned(scanResponse) } + val selectedUserWallet = userWalletsListManager.selectedUserWalletSync.guard { + Timber.e("Unable to create wallet, no user wallet selected") + return@launch + } + userWalletsListManager.update(selectedUserWallet.walletId) { userWallet -> + userWallet.copy( + scanResponse = userWallet.scanResponse.copy( + card = result.data, + ), + ) + } } - is CompletionResult.Failure -> {} + is CompletionResult.Failure -> Unit } } } @@ -209,50 +121,29 @@ class WalletMiddleware { store.dispatchOnMain(HomeAction.ReadCard(action.onScanSuccessEvent)) } } - is WalletAction.LoadCardInfo -> { - val attestationFailed = action.card.attestation.status == Attestation.Status.Failed - store.dispatchOnMain(GlobalAction.SetIfCardVerifiedOnline(!attestationFailed)) - - scope.launch { - val response = OnlineCardVerifier().getCardInfo(action.card.cardId, action.card.cardPublicKey) - when (response) { - is Result.Success -> { - val actionList = listOf( - WalletAction.SetArtworkId(response.data.artwork?.id), - WalletAction.LoadArtwork(action.card, response.data.artwork?.id), - ) - withMainContext { actionList.forEach { store.dispatch(it) } } - } - is Result.Failure -> {} - } - store.dispatchOnMain(WalletAction.Warnings.CheckIfNeeded) - } - } is WalletAction.LoadData, is WalletAction.LoadData.Refresh, -> { - val selectedWallet = userWalletsListManager.selectedUserWalletSync + val selectedWallet = userWalletsListManager.selectedUserWalletSync.guard { + Timber.e("Unable to load/refresh wallets data, no user wallet selected") + return + } scope.launch { - if (selectedWallet != null) { - globalState.tapWalletManager.loadData( - userWallet = selectedWallet, - refresh = action is WalletAction.LoadData.Refresh, - ) - } else { - Timber.e("Unable to load/refresh wallets data, no user wallet selected") - } + globalState.tapWalletManager.loadData( + userWallet = selectedWallet, + refresh = action is WalletAction.LoadData.Refresh, + ) } } is NetworkStateChanged -> { store.dispatch(WalletAction.Warnings.CheckHashesCount.CheckHashesCountOnline) if (!action.isOnline) return - val selectedUserWallet = userWalletsListManagerSafe?.selectedUserWalletSync - if (selectedUserWallet != null) { - scope.launch { globalState.tapWalletManager.loadData(selectedUserWallet) } - } else { + val selectedUserWallet = userWalletsListManagerSafe?.selectedUserWalletSync.guard { Timber.e("Unable to proceed with changed network state, no user wallet selected") + return } + scope.launch { globalState.tapWalletManager.loadData(selectedUserWallet) } } is WalletAction.CopyAddress -> { Analytics.send(Token.Receive.ButtonCopyAddress()) @@ -460,48 +351,6 @@ class WalletMiddleware { tokenRate = tokenRate, ) } - - private fun checkForRentWarning(walletManager: WalletManager?) { - val rentProvider = walletManager as? RentProvider ?: return - - scope.launch { - when (val result = rentProvider.minimalBalanceForRentExemption()) { - is com.tangem.blockchain.extensions.Result.Success -> { - fun isNeedToShowWarning(balance: BigDecimal, rentExempt: BigDecimal): Boolean { - return balance < rentExempt - } - - val balance = walletManager.wallet.fundsAvailable(AmountType.Coin) - val outgoingTxs = walletManager.wallet.getPendingTransactions( - PendingTransactionType.Outgoing, - ).filterByCoin() - - val rentExempt = result.data - val show = if (outgoingTxs.isEmpty()) { - isNeedToShowWarning(balance, rentExempt) - } else { - val outgoingAmount = outgoingTxs.sumOf { it.amountValue ?: BigDecimal.ZERO } - val rest = balance.minus(outgoingAmount) - isNeedToShowWarning(rest, rentExempt) - } - - val currency = walletManager.wallet.blockchain.currency - if (show) { - dispatchOnMain( - WalletAction.SetWalletRent( - wallet = walletManager.wallet, - minRent = "${rentProvider.rentAmount().stripZeroPlainString()} $currency", - rentExempt = "${rentExempt.stripZeroPlainString()} $currency", - ), - ) - } else { - dispatchOnMain(WalletAction.RemoveWalletRent(walletManager.wallet)) - } - } - is com.tangem.blockchain.extensions.Result.Failure -> {} - } - } - } } suspend fun handleBasicAnalyticsEvent() { diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/MultiWalletReducer.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/MultiWalletReducer.kt index e415a746a2..2dcfeaf198 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/MultiWalletReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/MultiWalletReducer.kt @@ -1,173 +1,13 @@ package com.tangem.tap.features.wallet.redux.reducers -import com.google.firebase.crashlytics.FirebaseCrashlytics -import com.tangem.blockchain.blockchains.polkadot.ExistentialDepositProvider -import com.tangem.blockchain.common.AmountType -import com.tangem.blockchain.common.Token -import com.tangem.blockchain.common.WalletManager -import com.tangem.tap.common.extensions.dispatchOnMain -import com.tangem.tap.common.extensions.dispatchToastNotification -import com.tangem.tap.common.extensions.getBlockchainTxHistory -import com.tangem.tap.common.extensions.getTokenTxHistory -import com.tangem.tap.common.extensions.toFiatString -import com.tangem.tap.common.extensions.toFormattedCurrencyString -import com.tangem.tap.common.redux.navigation.AppScreen -import com.tangem.tap.common.redux.navigation.NavigationAction -import com.tangem.tap.domain.tokens.models.BlockchainNetwork -import com.tangem.tap.features.wallet.models.Currency -import com.tangem.tap.features.wallet.models.WalletRent -import com.tangem.tap.features.wallet.models.filterByToken -import com.tangem.tap.features.wallet.models.getPendingTransactions -import com.tangem.tap.features.wallet.models.removeUnknownTransactions import com.tangem.tap.features.wallet.redux.ProgressState import com.tangem.tap.features.wallet.redux.WalletAction -import com.tangem.tap.features.wallet.redux.WalletData -import com.tangem.tap.features.wallet.redux.WalletMainButton import com.tangem.tap.features.wallet.redux.WalletState -import com.tangem.tap.features.wallet.redux.WalletState.Companion.UNKNOWN_AMOUNT_SIGN -import com.tangem.tap.features.wallet.redux.WalletStore -import com.tangem.tap.features.wallet.ui.BalanceStatus -import com.tangem.tap.features.wallet.ui.BalanceWidgetData -import com.tangem.tap.store -import com.tangem.tap.userWalletsListManager -import com.tangem.wallet.R -import java.math.BigDecimal class MultiWalletReducer { @Suppress("LongMethod", "ComplexMethod") fun reduce(action: WalletAction.MultiWallet, state: WalletState): WalletState { return when (action) { - is WalletAction.MultiWallet.AddBlockchains -> { - val walletStores: List = action.blockchains.map { blockchain -> - val walletManager = action.walletManagers.firstOrNull { - it.wallet.blockchain == blockchain.blockchain && - it.wallet.publicKey.derivationPath?.rawPath == blockchain.derivationPath - } - val wallet = walletManager?.wallet - val walletData = WalletData( - currencyData = BalanceWidgetData( - status = BalanceStatus.Loading, - currency = blockchain.blockchain.fullName, - currencySymbol = blockchain.blockchain.currency, - ), - walletAddresses = createAddressList(wallet), - mainButton = WalletMainButton.SendButton(false), - currency = Currency.Blockchain( - blockchain.blockchain, - blockchain.derivationPath, - ), - existentialDepositString = getExistentialDeposit(walletManager), - historyTransactions = walletManager?.getBlockchainTxHistory(), - ) - - WalletStore( - walletManager = walletManager, - blockchainNetwork = blockchain, - walletsData = listOf(walletData), - ) - } - - state.copy( - walletsStores = walletStores, - selectedCurrency = findSelectedCurrency( - walletsStores = walletStores, - currentSelectedCurrency = state.selectedCurrency, - isMultiWalletAllowed = state.isMultiwalletAllowed, - ), - ) - } - is WalletAction.MultiWallet.AddBlockchain -> { - val walletManager = action.walletManager ?: state.getWalletManager(action.blockchain) - val wallet = walletManager?.wallet - - val walletData = WalletData( - currencyData = BalanceWidgetData( - status = BalanceStatus.Loading, - currency = action.blockchain.blockchain.fullName, - currencySymbol = action.blockchain.blockchain.currency, - ), - walletAddresses = createAddressList(wallet), - mainButton = WalletMainButton.SendButton(false), - currency = Currency.Blockchain( - action.blockchain.blockchain, - action.blockchain.derivationPath, - ), - existentialDepositString = getExistentialDeposit(walletManager), - historyTransactions = walletManager?.getBlockchainTxHistory(), - ) - val walletStore = WalletStore( - walletManager = walletManager, - blockchainNetwork = action.blockchain, - walletsData = listOf(walletData), - ) - - val newState = state.updateWalletStore(walletStore) - if (wallet != null && wallet.amounts[AmountType.Coin]?.value != null) { - OnWalletLoadedReducer().reduce(wallet, action.blockchain, newState) - } else { - newState - } - } - is WalletAction.MultiWallet.AddTokens -> addTokens(action.tokens, action.blockchain, state) - is WalletAction.MultiWallet.AddToken -> addTokens(listOf(action.token), action.blockchain, state) - is WalletAction.MultiWallet.TokenLoaded -> { - val currency = Currency.fromBlockchainNetwork(action.blockchain, action.token) - val walletManager = state.getWalletManager(currency) - if (walletManager == null) { - val screen = if (userWalletsListManager.hasUserWallets) { - AppScreen.Welcome - } else { - AppScreen.Home - } - store.dispatchOnMain(NavigationAction.PopBackTo(screen)) - - FirebaseCrashlytics.getInstance().recordException( - IllegalStateException("MultiWallet.TokenLoaded: walletManager is null"), - ) - - store.dispatchToastNotification(R.string.internal_error_wallet_manager_not_found) - return state - } - val wallet = walletManager.wallet - val pendingTransactions = wallet.getPendingTransactions() - val tokenPendingTransactions = pendingTransactions.filterByToken(action.token) - val tokenBalanceStatus = when { - tokenPendingTransactions.isNotEmpty() -> BalanceStatus.TransactionInProgress - pendingTransactions.isNotEmpty() -> BalanceStatus.SameCurrencyTransactionInProgress - else -> BalanceStatus.VerifiedOnline - } - val tokenWalletData = state.getWalletData(currency) - val isTokenSendButtonEnabled = tokenWalletData?.shouldEnableTokenSendButton() == true && - pendingTransactions.isEmpty() - - val newTokenWalletData = tokenWalletData?.copy( - currencyData = tokenWalletData.currencyData.copy( - status = tokenBalanceStatus, - amount = action.amount.value, - amountFormatted = action.amount.value?.toFormattedCurrencyString( - decimals = action.amount.decimals, - currency = action.amount.currencySymbol, - ), - fiatAmountFormatted = tokenWalletData.fiatRate?.let { - action.amount.value?.toFiatString(it, store.state.globalState.appCurrency.symbol) - } ?: UNKNOWN_AMOUNT_SIGN, - blockchainAmount = wallet.amounts[AmountType.Coin]?.value ?: BigDecimal.ZERO, - ), - pendingTransactions = pendingTransactions.removeUnknownTransactions(), - mainButton = WalletMainButton.SendButton(isTokenSendButtonEnabled), - currency = Currency.Token( - token = action.token, - blockchain = action.blockchain.blockchain, - derivationPath = action.blockchain.derivationPath, - ), - walletRent = findWalletRent(state.getWalletStore(walletManager.wallet)), - historyTransactions = walletManager.getTokenTxHistory(action.token), - ) - state.updateWalletData(newTokenWalletData) - } - is WalletAction.MultiWallet.SetIsMultiwalletAllowed -> - state.copy(isMultiwalletAllowed = action.isMultiwalletAllowed) - is WalletAction.MultiWallet.SelectWallet -> { state.copy(selectedCurrency = action.currency) } @@ -175,16 +15,6 @@ class MultiWalletReducer { state.copy(selectedCurrency = action.currency) } is WalletAction.MultiWallet.TryToRemoveWallet -> state - is WalletAction.MultiWallet.RemoveWallet -> { - state.removeWalletData(state.getWalletData(action.currency)) - } - is WalletAction.MultiWallet.RemoveWallets -> { - var updatedState = state - action.currencies.forEach { updatedState = updatedState.removeWalletData(state.getWalletData(it)) } - updatedState - } - is WalletAction.MultiWallet.SaveCurrencies -> state - is WalletAction.MultiWallet.ShowWalletBackupWarning -> state.copy(showBackupWarning = action.show) is WalletAction.MultiWallet.ScheduleCheckForMissingDerivation -> state.copy( derivationsCheckIsScheduled = true, ) @@ -194,39 +24,7 @@ class MultiWalletReducer { ) is WalletAction.MultiWallet.BackupWallet -> state is WalletAction.MultiWallet.ScanToGetDerivations -> state.copy(state = ProgressState.Loading) + else -> state } } - - private fun findWalletRent(walletStore: WalletStore?): WalletRent? { - return walletStore?.walletsData?.firstOrNull { it.walletRent != null }?.walletRent - } - - private fun getExistentialDeposit(walletManager: WalletManager?): String? { - return (walletManager as? ExistentialDepositProvider)?.getExistentialDeposit()?.toPlainString() - } - - private fun addTokens(tokens: List, blockchain: BlockchainNetwork, state: WalletState): WalletState { - val wallets = tokens.mapNotNull { token -> token.toWallet(state, blockchain) } - return state.updateWalletsData(wallets) - } -} - -fun Token.toWallet(state: WalletState, blockchain: BlockchainNetwork): WalletData? { - val currency = Currency.fromBlockchainNetwork(blockchain, this) - if (state.currencies.contains(currency)) return null - - val walletManager = state.getWalletManager(currency) - val walletAddresses = createAddressList(walletManager?.wallet) - - return WalletData( - currencyData = BalanceWidgetData( - status = BalanceStatus.Loading, - currency = this.name, - currencySymbol = this.symbol, - ), - walletAddresses = walletAddresses, - mainButton = WalletMainButton.SendButton(false), - currency = currency, - historyTransactions = walletManager?.getTokenTxHistory(this), - ) } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/OnWalletLoadedReducer.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/OnWalletLoadedReducer.kt deleted file mode 100644 index 086fe57a83..0000000000 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/OnWalletLoadedReducer.kt +++ /dev/null @@ -1,154 +0,0 @@ -package com.tangem.tap.features.wallet.redux.reducers - -import com.tangem.blockchain.common.AmountType -import com.tangem.blockchain.common.Wallet -import com.tangem.common.extensions.isZero -import com.tangem.tap.common.extensions.toFiatValue -import com.tangem.tap.common.extensions.toFormattedCurrencyString -import com.tangem.tap.common.extensions.toFormattedFiatValue -import com.tangem.tap.domain.tokens.models.BlockchainNetwork -import com.tangem.tap.features.wallet.models.Currency -import com.tangem.tap.features.wallet.models.filterByToken -import com.tangem.tap.features.wallet.models.getPendingTransactions -import com.tangem.tap.features.wallet.models.removeUnknownTransactions -import com.tangem.tap.features.wallet.redux.ProgressState -import com.tangem.tap.features.wallet.redux.WalletMainButton -import com.tangem.tap.features.wallet.redux.WalletState -import com.tangem.tap.features.wallet.redux.WalletState.Companion.UNKNOWN_AMOUNT_SIGN -import com.tangem.tap.features.wallet.redux.replaceSomeWalletsData -import com.tangem.tap.features.wallet.ui.BalanceStatus -import com.tangem.tap.features.wallet.ui.BalanceWidgetData -import com.tangem.tap.store - -class OnWalletLoadedReducer { - - fun reduce(wallet: Wallet, blockchainNetwork: BlockchainNetwork, walletState: WalletState): WalletState { - return if (!walletState.isMultiwalletAllowed) { - onSingleWalletLoaded(wallet, walletState) - } else { - onMultiWalletLoaded(wallet, blockchainNetwork, walletState) - } - } - - @Suppress("LongMethod") - private fun onMultiWalletLoaded( - wallet: Wallet, - blockchainNetwork: BlockchainNetwork, - walletState: WalletState, - ): WalletState { - val walletData = walletState.getWalletData(blockchainNetwork) ?: return walletState - - val fiatCurrency = store.state.globalState.appCurrency - val coinAmountValue = wallet.amounts[AmountType.Coin]?.value - val formattedAmount = coinAmountValue?.toFormattedCurrencyString( - wallet.blockchain.decimals(), - wallet.blockchain.currency, - ) - - val pendingTransactions = wallet.getPendingTransactions() - val isCoinSendButtonEnabled = coinAmountValue?.isZero() == false && pendingTransactions.isEmpty() - val balanceStatus = if (pendingTransactions.isNotEmpty()) { - BalanceStatus.TransactionInProgress - } else { - BalanceStatus.VerifiedOnline - } - - val fiatAmount = walletData.fiatRate?.let { coinAmountValue?.toFiatValue(it) } - val fiatAmountFormatted = fiatAmount?.toFormattedFiatValue(fiatCurrency.symbol) ?: UNKNOWN_AMOUNT_SIGN - - val newWalletData = walletData.copy( - currencyData = walletData.currencyData.copy( - status = balanceStatus, - currency = wallet.blockchain.fullName, - currencySymbol = wallet.blockchain.currency, - blockchainAmount = coinAmountValue, - amount = coinAmountValue, - amountFormatted = formattedAmount, - fiatAmount = fiatAmount, - fiatAmountFormatted = fiatAmountFormatted, - ), - pendingTransactions = pendingTransactions.removeUnknownTransactions(), - mainButton = WalletMainButton.SendButton(isCoinSendButtonEnabled), - currency = Currency.fromBlockchainNetwork(blockchainNetwork), - ) - - val tokens = wallet.getTokens().mapNotNull { token -> - val currency = Currency.fromBlockchainNetwork(blockchainNetwork, token) - val tokenWalletData = walletState.getWalletData(currency) - val tokenPendingTransactions = pendingTransactions.filterByToken(token) - val tokenBalanceStatus = when { - tokenPendingTransactions.isNotEmpty() -> BalanceStatus.TransactionInProgress - pendingTransactions.isNotEmpty() -> BalanceStatus.SameCurrencyTransactionInProgress - else -> BalanceStatus.VerifiedOnline - } - val tokenAmountValue = wallet.getTokenAmount(token)?.value - val tokenFiatAmount = tokenWalletData?.fiatRate?.let { tokenAmountValue?.toFiatValue(it) } - val tokenFiatAmountFormatted = tokenFiatAmount?.toFormattedFiatValue(fiatCurrency.symbol) - ?: UNKNOWN_AMOUNT_SIGN - - val isTokenSendButtonEnabled = tokenWalletData?.shouldEnableTokenSendButton() == true && - pendingTransactions.isEmpty() - tokenWalletData?.copy( - currencyData = tokenWalletData.currencyData.copy( - status = tokenBalanceStatus, - blockchainAmount = coinAmountValue, - amount = tokenAmountValue, - amountFormatted = tokenAmountValue?.toFormattedCurrencyString( - token.decimals, - token.symbol, - ), - fiatAmount = tokenFiatAmount, - fiatAmountFormatted = tokenFiatAmountFormatted, - ), - pendingTransactions = tokenPendingTransactions.removeUnknownTransactions(), - mainButton = WalletMainButton.SendButton(isTokenSendButtonEnabled), - ) - } - val newWalletsData = tokens + newWalletData - val walletsData = walletState.walletsDataFromStores.replaceSomeWalletsData(newWalletsData) - - return walletState.updateWalletsData(walletsData) - } - - private fun onSingleWalletLoaded(wallet: Wallet, walletState: WalletState): WalletState { - if (wallet.blockchain != walletState.primaryBlockchain) return walletState - - val fiatCurrencyName = store.state.globalState.appCurrency.code - val amount = wallet.amounts[AmountType.Coin]?.value - val formattedAmount = amount?.toFormattedCurrencyString( - wallet.blockchain.decimals(), - wallet.blockchain.currency, - ) - val fiatAmount = walletState.primaryWalletData?.fiatRate?.let { amount?.toFiatValue(it) } - val fiatAmountFormatted = fiatAmount?.toFormattedFiatValue(fiatCurrencyName) ?: UNKNOWN_AMOUNT_SIGN - - val pendingTransactions = wallet.getPendingTransactions() - val sendButtonEnabled = amount?.isZero() == false && pendingTransactions.isEmpty() - val balanceStatus = if (pendingTransactions.isNotEmpty()) { - BalanceStatus.TransactionInProgress - } else { - BalanceStatus.VerifiedOnline - } - val walletData = walletState.primaryWalletData?.copy( - currencyData = BalanceWidgetData( - status = balanceStatus, - currency = wallet.blockchain.fullName, - currencySymbol = wallet.blockchain.currency, - blockchainAmount = amount, - amount = amount, - amountFormatted = formattedAmount, - fiatAmount = fiatAmount, - fiatAmountFormatted = fiatAmountFormatted, - ), - pendingTransactions = pendingTransactions.removeUnknownTransactions(), - mainButton = WalletMainButton.SendButton(sendButtonEnabled), - ) - val wallets = listOfNotNull(walletData) - val updatedStore = walletState.getWalletStore(walletData?.currency)?.updateWallets(wallets) - - return walletState.updateWalletStore(updatedStore).copy( - state = ProgressState.Done, - error = null, - ) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/TotalBalanceOperations.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/TotalBalanceOperations.kt index d5865ab287..ab255c0eb9 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/TotalBalanceOperations.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/TotalBalanceOperations.kt @@ -13,12 +13,6 @@ fun List.findProgressState(initialState: ProgressState = ProgressSta .reduce(ProgressState::or) } -fun List.calculateTotalFiatAmount(): BigDecimal { - return this - .map { it.currencyData.fiatAmount ?: BigDecimal.ZERO } - .reduce(BigDecimal::plus) -} - fun List.calculateTotalCryptoAmount(): BigDecimal { return this .map { it.currencyData.amount ?: BigDecimal.ZERO } diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/WalletReducer.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/WalletReducer.kt index d79d7a6339..78e56b7d93 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/WalletReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/WalletReducer.kt @@ -1,23 +1,13 @@ package com.tangem.tap.features.wallet.redux.reducers -import com.tangem.blockchain.common.AmountType import com.tangem.blockchain.common.Blockchain import com.tangem.blockchain.common.Wallet -import com.tangem.common.extensions.mapNotNullValues import com.tangem.domain.common.CardDTO import com.tangem.domain.common.TapWorkarounds.isTestCard -import com.tangem.domain.common.TwinCardNumber -import com.tangem.tap.common.entities.FiatCurrency -import com.tangem.tap.common.extensions.toFiatRateString -import com.tangem.tap.common.extensions.toFiatValue -import com.tangem.tap.common.extensions.toFormattedCurrencyString -import com.tangem.tap.common.extensions.toFormattedFiatValue import com.tangem.tap.common.redux.AppState import com.tangem.tap.domain.TapError -import com.tangem.tap.domain.extensions.getArtworkUrl import com.tangem.tap.domain.tokens.models.BlockchainNetwork import com.tangem.tap.features.wallet.models.Currency -import com.tangem.tap.features.wallet.models.WalletRent import com.tangem.tap.features.wallet.redux.AddressData import com.tangem.tap.features.wallet.redux.Artwork import com.tangem.tap.features.wallet.redux.ErrorType @@ -25,15 +15,12 @@ import com.tangem.tap.features.wallet.redux.ProgressState import com.tangem.tap.features.wallet.redux.WalletAction import com.tangem.tap.features.wallet.redux.WalletAddresses import com.tangem.tap.features.wallet.redux.WalletData -import com.tangem.tap.features.wallet.redux.WalletMainButton import com.tangem.tap.features.wallet.redux.WalletState import com.tangem.tap.features.wallet.redux.WalletStore -import com.tangem.tap.features.wallet.redux.replaceSomeWalletsData import com.tangem.tap.features.wallet.ui.BalanceStatus import com.tangem.tap.features.wallet.ui.BalanceWidgetData import com.tangem.tap.proxy.AppStateHolder import org.rekotlin.Action -import java.math.BigDecimal object WalletReducer { fun reduce(action: Action, state: AppState, appStateHolder: AppStateHolder): WalletState = @@ -43,7 +30,6 @@ object WalletReducer { @Suppress("LongMethod", "ComplexMethod") private fun internalReduce(action: Action, state: AppState, appStateHolder: AppStateHolder): WalletState { val multiWalletReducer = MultiWalletReducer() - val onWalletLoadedReducer = OnWalletLoadedReducer() val appCurrencyReducer = AppCurrencyReducer() if (action !is WalletAction) return state.walletState @@ -53,36 +39,6 @@ private fun internalReduce(action: Action, state: AppState, appStateHolder: AppS when (action) { is WalletAction.Warnings -> newState = handleCheckSignedHashesActions(action, newState) is WalletAction.MultiWallet -> newState = multiWalletReducer.reduce(action, newState) - - is WalletAction.ResetState -> { - newState = WalletState( - cardId = action.newCard.cardId, - walletCardsCount = action.newCard.findCardsCount(), - ) - } - is WalletAction.SetIfTestnetCard -> newState = newState.copy(isTestnet = action.isTestnet) - is WalletAction.EmptyWallet -> { - newState = newState.copy( - state = ProgressState.Done, - walletsStores = listOf( - WalletStore( - walletManager = null, - blockchainNetwork = BlockchainNetwork( - Blockchain.Unknown, - null, - emptyList(), - ), - walletsData = listOf( - WalletData( - currencyData = BalanceWidgetData(BalanceStatus.EmptyCard), - mainButton = WalletMainButton.CreateWalletButton(true), - currency = Currency.Blockchain(Blockchain.Unknown, null), - ), - ), - ), - ), - ) - } is WalletAction.LoadData.Failure -> { when (action.error) { is TapError.NoInternetConnection -> { @@ -133,7 +89,6 @@ private fun internalReduce(action: Action, state: AppState, appStateHolder: AppS } } } - is WalletAction.LoadData -> { newState = newState.copy( state = ProgressState.Loading, @@ -146,152 +101,6 @@ private fun internalReduce(action: Action, state: AppState, appStateHolder: AppS error = null, ) } - is WalletAction.LoadWallet -> { - val balanceStatus = if (newState.state == ProgressState.Refreshing) { - BalanceStatus.Refreshing - } else { - BalanceStatus.Loading - } - if (action.blockchain == null) { - val wallets = newState.walletsStores.map { walletStore -> - walletStore.copy( - walletsData = walletStore.walletsData.map { walletData -> - walletData.copy( - currencyData = walletData.currencyData.copy( - status = - if (walletStore.walletManager != null) balanceStatus else BalanceStatus.Unreachable, - currency = walletData.currencyData.currency, - currencySymbol = walletData.currencyData.currencySymbol, - ), - mainButton = WalletMainButton.SendButton(false), - ) - }, - ) - } - newState = newState.copy( - state = ProgressState.Loading, - walletsStores = wallets, - ) - } else { - val walletManager = newState.getWalletManager(action.blockchain) ?: return newState - val currencies = listOf(Currency.fromBlockchainNetwork(action.blockchain)) + - walletManager.cardTokens.map { - Currency.fromBlockchainNetwork(action.blockchain, it) - } - val newWalletsData = newState.walletsDataFromStores.filter { currencies.contains(it.currency) } - .map { wallet -> - wallet.copy( - currencyData = wallet.currencyData.copy( - status = balanceStatus, - currency = wallet.currencyData.currency, - currencySymbol = wallet.currencyData.currencySymbol, - ), - mainButton = WalletMainButton.SendButton(false), - ) - } - val walletsData = newState.walletsDataFromStores.replaceSomeWalletsData(newWalletsData) - val walletStore = newState.getWalletStore(action.blockchain)?.updateWallets(walletsData) - newState = newState.updateWalletStore(walletStore) - } - } - is WalletAction.LoadWallet.Success -> newState = onWalletLoadedReducer.reduce( - wallet = action.wallet, - blockchainNetwork = action.blockchain, - walletState = newState, - ) - is WalletAction.LoadWallet.NoAccount -> { - val amount = BigDecimal.ZERO - val fiatAmount = BigDecimal.ZERO - val walletData = newState.getWalletData(action.blockchain)?.let { walletData -> - val walletBlockchain = walletData.currency.blockchain - walletData.copy( - currencyData = BalanceWidgetData( - status = BalanceStatus.NoAccount, - currency = action.wallet.blockchain.fullName, - currencySymbol = action.wallet.blockchain.currency, - amount = amount, - amountFormatted = amount.toFormattedCurrencyString( - decimals = walletBlockchain.decimals(), - currency = walletBlockchain.currency, - ), - fiatAmount = fiatAmount, - fiatAmountFormatted = fiatAmount.toFormattedFiatValue( - fiatCurrencyName = state.globalState.appCurrency.symbol, - ), - amountToCreateAccount = action.amountToCreateAccount, - ), - ) - } - val updatedWalletStore = newState.getWalletStore(action.blockchain) - ?.updateWallets(listOfNotNull(walletData)) - - newState = newState.updateWalletStore(updatedWalletStore) - } - - is WalletAction.LoadWallet.Failure -> { - val message = if (newState.error == ErrorType.NoInternetConnection) { - null - } else { - action.errorMessage - } - val walletStore = newState.getWalletStore(action.wallet) - val walletData = walletStore?.walletsData?.first { it.currency is Currency.Blockchain } - val newWalletData = walletData?.copy( - currencyData = walletData.currencyData.copy( - status = BalanceStatus.Unreachable, - errorMessage = message, - ), - ) - val tokenWallets = action.wallet.getTokens() - .mapNotNull { token -> - walletStore?.blockchainNetwork?.let { - newState.getWalletData(Currency.fromBlockchainNetwork(it, token)) - } - } - .map { - it.copy( - currencyData = it.currencyData.copy( - status = BalanceStatus.Unreachable, - errorMessage = message, - ), - ) - } - val updatedWallets = walletStore!!.updateWallets(listOfNotNull(newWalletData) + tokenWallets).walletsData - - newState = newState.updateWalletsData(updatedWallets) - - val progressState = - if (newState.walletsDataFromStores.any { it.currencyData.status == BalanceStatus.Loading }) { - ProgressState.Loading - } else { - ProgressState.Done - } - - newState = newState.copy( - state = progressState, - ) - } - is WalletAction.SetArtworkId -> { - val cardImage = if (newState.cardImage?.artworkId == action.artworkId) { - newState.cardImage - } else { - null - } - newState = newState.copy(cardImage = cardImage) - } - - is WalletAction.LoadFiatRate.Success -> { - newState = setNewFiatRate(action.fiatRates, state.globalState.appCurrency, newState) - } - is WalletAction.LoadArtwork -> { - val artworkUrl = action.card.getArtworkUrl(action.artworkId) - ?: when (state.twinCardsState.cardNumber) { - TwinCardNumber.First -> Artwork.TWIN_CARD_1 - TwinCardNumber.Second -> Artwork.TWIN_CARD_2 - else -> Artwork.DEFAULT_IMG_URL - } - newState = newState.copy(cardImage = Artwork(artworkId = artworkUrl)) - } is WalletAction.TradeCryptoAction -> return newState is WalletAction.ChangeSelectedAddress -> { val walletAddresses = newState.getWalletData(newState.selectedCurrency)?.walletAddresses @@ -308,22 +117,9 @@ private fun internalReduce(action: Action, state: AppState, appStateHolder: AppS ), ) } - is WalletAction.SetWalletRent -> { - val walletStore = newState.getWalletStore(action.wallet) ?: return newState - val walletRent = WalletRent(action.minRent, action.rentExempt) - val walletsData = walletStore.walletsData.map { it.copy(walletRent = walletRent) } - newState = newState.updateWalletsData(walletsData) - } - is WalletAction.RemoveWalletRent -> { - val walletStore = newState.getWalletStore(action.wallet) ?: return newState - val walletsData = walletStore.walletsData.map { it.copy(walletRent = null) } - newState = newState.updateWalletsData(walletsData) - } is WalletAction.AppCurrencyAction -> { newState = appCurrencyReducer.reduce(action, newState) } - is WalletAction.UserTokens.Loading -> newState = newState.copy(loadingUserTokens = true) - is WalletAction.UserTokens.Loaded -> newState = newState.copy(loadingUserTokens = false) is WalletAction.UserWalletChanged -> with(action.userWallet) { val card = scanResponse.card newState = WalletState( @@ -395,20 +191,6 @@ private fun CardDTO.findCardsCount(): Int? { return (this.backupStatus as? CardDTO.BackupStatus.Active)?.cardCount?.inc() } -fun createAddressList(wallet: Wallet?, walletAddresses: WalletAddresses? = null): WalletAddresses? { - if (wallet == null) return null - - val listOfAddressData = wallet.createAddressesData() - // restore a selected wallet address - var indexOfSelectedWallet = 0 - walletAddresses?.let { - val index = - listOfAddressData.indexOfFirst { it.address == walletAddresses.selectedAddress.address } - if (index != -1) indexOfSelectedWallet = index - } - return WalletAddresses(listOfAddressData[indexOfSelectedWallet], listOfAddressData) -} - fun Wallet.createAddressesData(): List { val listOfAddressData = mutableListOf() // put a defaultAddress at the first place @@ -440,41 +222,4 @@ private fun handleCheckSignedHashesActions( is WalletAction.Warnings.Set -> state.copy(mainWarningsList = action.warningList) else -> state } -} - -private fun setNewFiatRate( - fiatRates: Map, - appCurrency: FiatCurrency, - state: WalletState, -): WalletState { - val rateFormatter: (BigDecimal) -> String = { rate: BigDecimal -> - rate.toFiatRateString(fiatCurrencyName = appCurrency.symbol) - } - - val newWalletsData = fiatRates.mapNotNullValues { it.value }.mapNotNull { (currency, rate) -> - val walletStore = state.getWalletStore(currency) ?: return@mapNotNull null - val wallet = walletStore.walletManager?.wallet - val walletData = state.getWalletData(currency) ?: return@mapNotNull null - val currencyData = walletData.currencyData - - var fiatAmount = when (currency) { - is Currency.Blockchain -> wallet?.amounts?.get(AmountType.Coin)?.value?.toFiatValue(rate) - is Currency.Token -> wallet?.getTokenAmount(currency.token)?.value?.toFiatValue(rate) - } - if (currencyData.status == BalanceStatus.NoAccount && fiatAmount == null) { - fiatAmount = BigDecimal.ZERO.setScale(2) - } - val fiatAmountFormatted = fiatAmount?.toFormattedFiatValue(appCurrency.symbol) - - walletData.copy( - currencyData = currencyData.copy( - fiatAmountFormatted = fiatAmountFormatted, - fiatAmount = fiatAmount, - ), - fiatRate = rate, - fiatRateString = rateFormatter(rate), - ) - } - - return state.updateWalletsData(newWalletsData) } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletDetailsFragment.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletDetailsFragment.kt index 3ea64226b5..af465cf7b2 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletDetailsFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletDetailsFragment.kt @@ -20,8 +20,10 @@ import com.badoo.mvicore.DiffStrategy import com.badoo.mvicore.ModelWatcher import com.badoo.mvicore.modelWatcher import com.tangem.common.doOnResult +import com.tangem.common.extensions.guard import com.tangem.core.analytics.Analytics import com.tangem.domain.common.TapWorkarounds.derivationStyle +import com.tangem.domain.common.extensions.withMainContext import com.tangem.feature.swap.domain.SwapInteractor import com.tangem.tangem_sdk_new.extensions.dpToPx import com.tangem.tap.common.SnackbarHandler @@ -61,7 +63,6 @@ import com.tangem.wallet.databinding.FragmentWalletDetailsBinding import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext import org.rekotlin.StoreSubscriber import timber.log.Timber import javax.inject.Inject @@ -247,17 +248,16 @@ class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details), if (currencyData.status != BalanceStatus.Loading && currencyData.status != BalanceStatus.Refreshing) { Analytics.send(Token.Refreshed()) lifecycleScope.launch(Dispatchers.Default) { - val selectedUserWallet = userWalletsListManagerSafe?.selectedUserWalletSync - if (selectedUserWallet != null) { - walletCurrenciesManager.update(selectedUserWallet, currency) - .doOnResult { - withContext(Dispatchers.Main) { - binding.srlWalletDetails.isRefreshing = false - } - } - } else { + val selectedUserWallet = userWalletsListManagerSafe?.selectedUserWalletSync.guard { Timber.e("Unable to refresh wallet details screen, no user wallet selected") + return@launch } + walletCurrenciesManager.update(selectedUserWallet, currency) + .doOnResult { + withMainContext { + binding.srlWalletDetails.isRefreshing = false + } + } } } } diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/saltPay/SaltPayWalletView.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/saltPay/SaltPayWalletView.kt index e73d4ab074..a379f290a8 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/saltPay/SaltPayWalletView.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/saltPay/SaltPayWalletView.kt @@ -3,7 +3,6 @@ package com.tangem.tap.features.wallet.ui.wallet.saltPay import android.view.LayoutInflater import android.view.ViewGroup import com.tangem.core.analytics.Analytics -import com.tangem.domain.common.extensions.debounce import com.tangem.domain.common.extensions.withMainContext import com.tangem.tap.common.ShimmerData import com.tangem.tap.common.ShimmerRecyclerAdapter @@ -21,7 +20,6 @@ import com.tangem.tap.features.wallet.ui.wallet.WalletView import com.tangem.tap.features.wallet.ui.wallet.saltPay.rv.HistoryItemData import com.tangem.tap.features.wallet.ui.wallet.saltPay.rv.HistoryTransactionData import com.tangem.tap.features.wallet.ui.wallet.saltPay.rv.TxHistoryAdapter -import com.tangem.tap.mainScope import com.tangem.tap.network.exchangeServices.CurrencyExchangeManager import com.tangem.tap.scope import com.tangem.tap.store @@ -31,7 +29,6 @@ import com.tangem.wallet.databinding.LayoutSaltPayBalanceBinding import com.tangem.wallet.databinding.LayoutSaltPayTxHistoryBinding import com.tangem.wallet.databinding.LayoutSaltPayWalletBinding import kotlinx.coroutines.launch -import org.rekotlin.Action import timber.log.Timber /** @@ -41,8 +38,6 @@ class SaltPayWalletView : WalletView() { private var saltPayBinding: LayoutSaltPayWalletBinding? = null - private val actionDebouncer = debounce(500, mainScope) { store.dispatch(it) } - private val balanceWidget: LayoutSaltPayBalanceBinding? get() = saltPayBinding?.lSaltPayBalance @@ -131,7 +126,6 @@ class SaltPayWalletView : WalletView() { if (tokenData.currencyData.fiatAmount == null) { veilBalance.veil() - actionDebouncer(WalletAction.LoadFiatRate()) } else { veilBalance.unVeil() tvBalance.text = tokenData.currencyData.fiatAmount.formatAmountAsSpannedString( diff --git a/app/src/main/java/com/tangem/tap/proxy/UserWalletManagerImpl.kt b/app/src/main/java/com/tangem/tap/proxy/UserWalletManagerImpl.kt index fac9f927b3..c0c0c1ac91 100644 --- a/app/src/main/java/com/tangem/tap/proxy/UserWalletManagerImpl.kt +++ b/app/src/main/java/com/tangem/tap/proxy/UserWalletManagerImpl.kt @@ -4,6 +4,7 @@ import com.tangem.blockchain.common.AmountType import com.tangem.blockchain.common.Blockchain import com.tangem.blockchain.common.Token import com.tangem.blockchain.common.WalletManager +import com.tangem.common.extensions.guard import com.tangem.domain.common.extensions.fromNetworkId import com.tangem.domain.common.extensions.toCoinId import com.tangem.domain.common.extensions.toNetworkId @@ -102,15 +103,14 @@ class UserWalletManagerImpl( val blockchain = requireNotNull(Blockchain.fromNetworkId(currency.networkId)) { "blockchain not found" } val blockchainNetwork = BlockchainNetwork(blockchain, card) - val selectedUserWallet = userWalletsListManager.selectedUserWalletSync - if (selectedUserWallet != null) { - walletCurrenciesManager.addCurrencies( - userWallet = selectedUserWallet, - currenciesToAdd = listOf(currency.toWalletCurrency(blockchainNetwork)), - ) - } else { - Timber.e("Unable to add token, selected user wallet is null") + val selectedUserWallet = userWalletsListManager.selectedUserWalletSync.guard { + Timber.e("Unable to add token, no user wallet selected") + return } + walletCurrenciesManager.addCurrencies( + userWallet = selectedUserWallet, + currenciesToAdd = listOf(currency.toWalletCurrency(blockchainNetwork)), + ) } override fun getWalletAddress(networkId: String): String { From 6846deed7448bc241c53a45be658a45763fa0421 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 1 Mar 2023 12:38:45 +0300 Subject: [PATCH 13/17] Updated on 2026-08-14 --- .../features/wallet/ui/wallet/MultiWalletView.kt | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/MultiWalletView.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/MultiWalletView.kt index d1eab75515..4d2bdc1fdc 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/MultiWalletView.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/MultiWalletView.kt @@ -1,10 +1,8 @@ package com.tangem.tap.features.wallet.ui.wallet import android.widget.Button -import androidx.core.view.isGone import androidx.core.view.isVisible import androidx.recyclerview.widget.LinearLayoutManager -import com.badoo.mvicore.DiffStrategy import com.badoo.mvicore.modelWatcher import com.tangem.core.analytics.Analytics import com.tangem.domain.common.TapWorkarounds.derivationStyle @@ -20,7 +18,6 @@ import com.tangem.tap.common.redux.navigation.NavigationAction import com.tangem.tap.domain.tokens.CurrenciesRepository import com.tangem.tap.features.tokens.redux.TokensAction import com.tangem.tap.features.wallet.models.TotalBalance -import com.tangem.tap.features.wallet.redux.ProgressState import com.tangem.tap.features.wallet.redux.WalletAction import com.tangem.tap.features.wallet.redux.WalletState import com.tangem.tap.features.wallet.ui.BalanceStatus @@ -36,13 +33,6 @@ class MultiWalletView : WalletView() { private lateinit var walletsAdapter: WalletAdapter private val watcher = modelWatcher { - val totalBalanceStrategy: DiffStrategy = { old, new -> - old.cardId != new.cardId || - old.totalBalance != new.totalBalance || - old.state != new.state || - old.walletsStores.size != new.walletsStores.size - } - // !!! Workaround !!! // Checking state properties instead of state params can reduce application performance, // but here it is necessary because the WalletStore has an unsuitable equals method @@ -67,12 +57,11 @@ class MultiWalletView : WalletView() { handleBackupWarning(it, showBackupWarnings) } } - watch({ it }, totalBalanceStrategy) { walletState -> + (WalletState::totalBalance or WalletState::walletsDataFromStores) { walletState -> binding?.let { handleTotalBalance( binding = it, totalBalance = walletState.totalBalance, - progressState = walletState.state, walletsCount = walletState.walletsDataFromStores.size, ) } @@ -179,10 +168,9 @@ class MultiWalletView : WalletView() { private fun handleTotalBalance( binding: FragmentWalletBinding, totalBalance: TotalBalance?, - progressState: ProgressState, walletsCount: Int, ) = with(binding.lCardTotalBalance) { - isGone = progressState == ProgressState.Done && walletsCount == 0 + isVisible = walletsCount > 0 onChangeFiatCurrencyClick = { store.dispatch(WalletAction.AppCurrencyAction.ChooseAppCurrency) From 16771eedb8cb1a8c86287663f1b05f449935d608 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 2 Mar 2023 15:12:49 +0300 Subject: [PATCH 14/17] Updated on 2026-08-14 --- .../java/com/tangem/datasource/api/common/MoshiConverter.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/common/MoshiConverter.kt b/core/datasource/src/main/java/com/tangem/datasource/api/common/MoshiConverter.kt index 1457807f3e..510ac95179 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/common/MoshiConverter.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/common/MoshiConverter.kt @@ -3,6 +3,7 @@ package com.tangem.datasource.api.common import com.squareup.moshi.Moshi import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory import com.tangem.common.json.MoshiJsonConverter +import com.tangem.common.json.TangemSdkAdapter import retrofit2.converter.moshi.MoshiConverterFactory /** @@ -15,6 +16,7 @@ object MoshiConverter { val networkMoshi: Moshi = Moshi.Builder() .add(KotlinJsonAdapterFactory()) .add(BigDecimalAdapter()) + .add(TangemSdkAdapter.ByteArrayAdapter()) .build() val networkMoshiConverter: MoshiConverterFactory = MoshiConverterFactory.create(networkMoshi) From 21f575fb13624bedd44fe30329fc3c421994b3ba Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 2 Mar 2023 15:32:41 +0300 Subject: [PATCH 15/17] Updated on 2026-08-14 --- buildSrc/src/main/java/Versions.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/buildSrc/src/main/java/Versions.kt b/buildSrc/src/main/java/Versions.kt index 0667a708c7..adb41a5c61 100644 --- a/buildSrc/src/main/java/Versions.kt +++ b/buildSrc/src/main/java/Versions.kt @@ -61,10 +61,10 @@ object Versions { // endregion Other libraries // region Tangem - const val tangemBlockchainSdk = "develop-172" + const val tangemBlockchainSdk = "develop-173" // const val tangemBlockchainSdk = "0.0.1" // Keep it! - used for local builds - const val tangemCardSdk = "develop-191" + const val tangemCardSdk = "develop-198" // const val tangemCardSdk = "0.0.1" // Keep it! - used for local builds // endregion Tangem From 95fea5388f773421548116079bbec06da80c24d2 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 2 Mar 2023 15:57:52 +0300 Subject: [PATCH 16/17] Updated on 2026-08-14 --- .../tap/features/wallet/redux/WalletData.kt | 1 - .../tangem/feature/swap/SwapRepositoryImpl.kt | 25 +++++- .../feature/swap/domain/SwapInteractorImpl.kt | 86 +++++++++---------- .../swap/domain/models/ui/SwapState.kt | 8 +- features/swap/presentation/build.gradle.kts | 1 + .../feature/swap/ui/SwapScreenContent.kt | 2 + .../feature/swap/viewmodels/SwapViewModel.kt | 6 +- 7 files changed, 81 insertions(+), 48 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletData.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletData.kt index e5edb01fa4..3dcbf37cb7 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletData.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletData.kt @@ -92,7 +92,6 @@ data class WalletData( ) walletWarnings.add(warning) } - } private fun assembleTokenWarnings(walletWarnings: MutableList) = with(currency) { diff --git a/features/swap/data/src/main/java/com/tangem/feature/swap/SwapRepositoryImpl.kt b/features/swap/data/src/main/java/com/tangem/feature/swap/SwapRepositoryImpl.kt index 199838f94c..e0671b7ffd 100644 --- a/features/swap/data/src/main/java/com/tangem/feature/swap/SwapRepositoryImpl.kt +++ b/features/swap/data/src/main/java/com/tangem/feature/swap/SwapRepositoryImpl.kt @@ -35,8 +35,24 @@ internal class SwapRepositoryImpl @Inject constructor( private val approveConverter = ApproveConverter() override suspend fun getRates(currencyId: String, tokenIds: List): Map { + // workaround cause backend do not return arbitrum and optimism rates + val addedTokens = if (tokenIds.contains(OPTIMISM_ID) || tokenIds.contains(ARBITRUM_ID)) { + tokenIds.toMutableList().apply { + add(ETHEREUM_ID) + } + } else { + tokenIds + } return withContext(coroutineDispatcher.io) { - tangemTechApi.getRates(currencyId.lowercase(), tokenIds.joinToString(",")).rates + val rates = tangemTechApi.getRates(currencyId.lowercase(), addedTokens.joinToString(",")).rates + val ethRate = rates[ETHEREUM_ID] + rates.mapValues { + if (it.key == OPTIMISM_ID || it.key == ARBITRUM_ID) { + ethRate ?: 0.0 + } else { + it.value + } + } } } @@ -136,4 +152,11 @@ internal class SwapRepositoryImpl @Inject constructor( private fun getOneInchApi(networkId: String): OneInchApi { return oneInchApiFactory.getApi(networkId) } + + companion object { + // TODO("get this ids from blockchain enum later") + private const val OPTIMISM_ID = "optimistic-ethereum" + private const val ARBITRUM_ID = "arbitrum-one" + private const val ETHEREUM_ID = "ethereum" + } } \ 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 23b6c794b8..3b78f398f7 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 @@ -120,7 +120,7 @@ internal class SwapInteractorImpl @Inject constructor( val result = transactionManager.sendApproveTransaction( networkId = networkId, feeAmount = fee, - estimatedGas = increasedEstimatedGas, + estimatedGas = estimatedGas, destinationAddress = transactionData.toAddress, dataToSign = transactionData.data, ) @@ -152,38 +152,54 @@ internal class SwapInteractorImpl @Inject constructor( val fromTokenAddress = getTokenAddress(fromToken) val toTokenAddress = getTokenAddress(toToken) val isAllowedToSpend = checkAllowance(networkId, fromTokenAddress) - val fee = getAndUpdateFee(networkId, fromToken) - val isBalanceEnough = isBalanceEnough(fromToken, amount, fee) - val isFeeEnough = checkFeeIsEnough(fee, amount, networkId, fromToken) if (isAllowedToSpend && allowPermissionsHandler.isAddressAllowanceInProgress(fromTokenAddress)) { allowPermissionsHandler.removeAddressFromProgress(fromTokenAddress) transactionManager.updateWalletManager(networkId) } + // load initial quotes data, it works despite balance + val quotesData = loadQuoteData( + networkId = networkId, + fromTokenAddress = fromTokenAddress, + toTokenAddress = toTokenAddress, + amount = amount, + fromToken = fromToken, + toToken = toToken, + ) + // get fee from loaded quotes data, if error, use blockchain fee for 0 amount tx + val fee = getInchFee(quotesData) + val isFeeEnough = checkFeeIsEnough( + fee = fee, + spendAmount = amount, + networkId = networkId, + fromToken = fromToken, + ) + val isBalanceEnough = isBalanceEnough(fromToken, amount, fee) val preparedSwapConfigState = PreparedSwapConfigState( isAllowedToSpend = isAllowedToSpend, isBalanceEnough = isBalanceEnough, isFeeEnough = isFeeEnough, ) return if (isAllowedToSpend && isBalanceEnough && isFeeEnough) { - loadSwapData( + // if enough balance, fee and spend was allowed, request swap data + val swapData = loadSwapData( networkId = networkId, fromTokenAddress = fromTokenAddress, toTokenAddress = toTokenAddress, fromToken = fromToken, toToken = toToken, amount = amount, - preparedSwapConfigState = preparedSwapConfigState, ) + if (swapData is SwapState.QuotesLoadedState) { + swapData.copy(preparedSwapConfigState = preparedSwapConfigState) + } else { + swapData + } } else { - loadQuoteData( - networkId = networkId, - fromTokenAddress = fromTokenAddress, - toTokenAddress = toTokenAddress, - amount = amount, - fromToken = fromToken, - toToken = toToken, - preparedSwapConfigState = preparedSwapConfigState, - ) + if (quotesData is SwapState.QuotesLoadedState) { + quotesData.copy(preparedSwapConfigState = preparedSwapConfigState) + } else { + quotesData + } } } @@ -195,8 +211,7 @@ internal class SwapInteractorImpl @Inject constructor( amountToSwap: String, ): TxState { val amount = requireNotNull(toBigDecimalOrNull(amountToSwap)) { "wrong amount format, use only digits" } - val estimatedGas = - increaseByPercents(TWENTY_FIVE_PERCENTS, swapData.transaction.gas.toIntOrNull() ?: DEFAULT_GAS) + val estimatedGas = swapData.transaction.gas.toIntOrNull() ?: DEFAULT_GAS val fee = transactionManager.calculateFee( networkId = networkId, gasPrice = swapData.transaction.gasPrice, @@ -250,6 +265,12 @@ internal class SwapInteractorImpl @Inject constructor( } } + private fun getInchFee(quotesData: SwapState): BigDecimal? { + return if (quotesData is SwapState.QuotesLoadedState) { + quotesData.feeRaw + } else null + } + private fun selectToToken( initialToken: Currency, tokensInWallet: List, @@ -294,25 +315,6 @@ internal class SwapInteractorImpl @Inject constructor( } } - private suspend fun getAndUpdateFee(networkId: String, fromToken: Currency): BigDecimal? { - val lastFee = cache.getLastFeeForNetwork(networkId) - if (lastFee == null) { - if (userWalletManager.getNativeTokenBalance(networkId)?.value?.compareTo(BigDecimal.ZERO) == 0) { - return null - } - val transactionData = repository.dataToApprove(networkId, getTokenAddress(fromToken)) - val fee = transactionManager.getFee( - networkId, - BigDecimal.ZERO, - cryptoCurrencyConverter.convert(fromToken), - transactionData.toAddress, - ).value - cache.cacheLastFeeForNetwork(fee, networkId) - return fee - } - return lastFee - } - private suspend fun checkAllowance(networkId: String, fromTokenAddress: String): Boolean { val allowance = repository.checkTokensSpendAllowance( networkId = networkId, @@ -351,7 +353,6 @@ internal class SwapInteractorImpl @Inject constructor( amount: SwapAmount, fromToken: Currency, toToken: Currency, - preparedSwapConfigState: PreparedSwapConfigState, ): SwapState { repository.findBestQuote( networkId = networkId, @@ -380,7 +381,7 @@ internal class SwapInteractorImpl @Inject constructor( fromTokenAmount = quoteDataModel.fromTokenAmount, toTokenAmount = quoteDataModel.toTokenAmount, formattedFee = formattedFee, - preparedSwapConfigState = preparedSwapConfigState, + feeRaw = fee, swapDataModel = null, ) return updatePermissionState( @@ -422,7 +423,6 @@ internal class SwapInteractorImpl @Inject constructor( fromToken: Currency, toToken: Currency, amount: SwapAmount, - preparedSwapConfigState: PreparedSwapConfigState, ): SwapState { repository.prepareSwapTransaction( networkId = networkId, @@ -452,8 +452,8 @@ internal class SwapInteractorImpl @Inject constructor( fromTokenAmount = swapData.fromTokenAmount, toTokenAmount = swapData.toTokenAmount, formattedFee = formattedFee, - preparedSwapConfigState = preparedSwapConfigState, swapDataModel = swapData, + feeRaw = fee, ) return swapState.copy( permissionState = PermissionDataState.Empty, @@ -472,7 +472,7 @@ internal class SwapInteractorImpl @Inject constructor( fromTokenAmount: SwapAmount, toTokenAmount: SwapAmount, formattedFee: String, - preparedSwapConfigState: PreparedSwapConfigState, + feeRaw: BigDecimal, swapDataModel: SwapDataModel?, ): SwapState.QuotesLoadedState { val appCurrency = userWalletManager.getUserAppCurrency() @@ -511,9 +511,9 @@ internal class SwapInteractorImpl @Inject constructor( toRate = rates[toToken.id] ?: 0.0, ), networkCurrency = userWalletManager.getNetworkCurrency(networkId), - preparedSwapConfigState = preparedSwapConfigState, swapDataModel = swapDataModel, tangemFee = getTangemFee(), + feeRaw = feeRaw, ) } @@ -639,7 +639,7 @@ internal class SwapInteractorImpl @Inject constructor( private const val DEFAULT_GAS = 300000 private const val DEFAULT_BLOCKCHAIN_INCH_ADDRESS = "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE" private const val TWENTY_FIVE_PERCENTS = 25 - private const val INCREASE_FEE_TO_CHECK_ENOUGH_PERCENT = 1.5 + private const val INCREASE_FEE_TO_CHECK_ENOUGH_PERCENT = 1.4 private const val USDT_SYMBOL = "USDT" private const val USDC_SYMBOL = "USDC" private const val INFINITY_SYMBOL = "∞" diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/ui/SwapState.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/ui/SwapState.kt index 1515683e56..deace8a843 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/ui/SwapState.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/ui/SwapState.kt @@ -5,6 +5,7 @@ import com.tangem.feature.swap.domain.models.SwapAmount import com.tangem.feature.swap.domain.models.domain.ApproveModel import com.tangem.feature.swap.domain.models.domain.PreparedSwapConfigState import com.tangem.feature.swap.domain.models.domain.SwapDataModel +import java.math.BigDecimal sealed interface SwapState { @@ -14,10 +15,15 @@ sealed interface SwapState { val fee: String, val priceImpact: Float, val networkCurrency: String, - val preparedSwapConfigState: PreparedSwapConfigState, + val preparedSwapConfigState: PreparedSwapConfigState = PreparedSwapConfigState( + isAllowedToSpend = false, + isBalanceEnough = false, + isFeeEnough = false, + ), val permissionState: PermissionDataState = PermissionDataState.Empty, val swapDataModel: SwapDataModel? = null, val tangemFee: Double, + val feeRaw: BigDecimal, ) : SwapState data class EmptyAmountState( diff --git a/features/swap/presentation/build.gradle.kts b/features/swap/presentation/build.gradle.kts index 684ec4d57c..287b9e6ad6 100644 --- a/features/swap/presentation/build.gradle.kts +++ b/features/swap/presentation/build.gradle.kts @@ -74,6 +74,7 @@ dependencies { /** Other libraries */ implementation(Library.composeShimmer) implementation(Library.kotlinSerialization) + implementation(Library.timber) /** DI */ implementation(Library.hilt) diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapScreenContent.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapScreenContent.kt index 1b52c30bb6..0d9a788af5 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapScreenContent.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapScreenContent.kt @@ -41,6 +41,7 @@ import com.tangem.core.ui.components.SimpleOkDialog import com.tangem.core.ui.components.SmallInfoCard import com.tangem.core.ui.components.SmallInfoCardWithDisclaimer import com.tangem.core.ui.components.SmallInfoCardWithWarning +import com.tangem.core.ui.components.SpacerH8 import com.tangem.core.ui.components.WarningCard import com.tangem.core.ui.components.appbar.AppBarWithBackButton import com.tangem.core.ui.components.keyboardAsState @@ -343,6 +344,7 @@ private fun SwapWarnings( // ) // } } + SpacerH8() } } } diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/viewmodels/SwapViewModel.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/viewmodels/SwapViewModel.kt index ddf5f6876c..8851cc69d2 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/viewmodels/SwapViewModel.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/viewmodels/SwapViewModel.kt @@ -1,6 +1,5 @@ package com.tangem.feature.swap.viewmodels -import android.util.Log import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue @@ -32,6 +31,7 @@ import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.launch import kotlinx.serialization.decodeFromString import kotlinx.serialization.json.Json +import timber.log.Timber import java.text.DecimalFormat import java.text.NumberFormat import java.util.* @@ -122,7 +122,7 @@ internal class SwapViewModel @Inject constructor( ) } .onFailure { - Log.e("SwapViewModel", it.message ?: it.cause.toString()) + Timber.e(it) } } } @@ -198,11 +198,13 @@ internal class SwapViewModel @Inject constructor( ) } is SwapState.SwapError -> { + Timber.e("SwapError when loading quotes ${swapState.error}") uiState = stateBuilder.mapError(uiState, swapState.error) { startLoadingQuotesFromLastState() } } } }, onError = { + Timber.e("Error when loading quotes: $it") uiState = stateBuilder.addWarning(uiState, null) { startLoadingQuotesFromLastState() } }, ) From 5a9d064e5db2f7959e71f2917b9f6e8e5315deee Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 2 Mar 2023 13:30:41 +0300 Subject: [PATCH 17/17] Updated on 2026-08-14 --- .../tap/domain/tokens/UserTokensRepository.kt | 44 ++++++++++++------- 1 file changed, 29 insertions(+), 15 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/domain/tokens/UserTokensRepository.kt b/app/src/main/java/com/tangem/tap/domain/tokens/UserTokensRepository.kt index 7d01846702..03b431d3e2 100644 --- a/app/src/main/java/com/tangem/tap/domain/tokens/UserTokensRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/tokens/UserTokensRepository.kt @@ -19,6 +19,7 @@ import com.tangem.tap.network.NetworkConnectivity import com.tangem.utils.coroutines.AppCoroutineDispatcherProvider import com.tangem.utils.coroutines.CoroutineDispatcherProvider import kotlinx.coroutines.withContext +import timber.log.Timber class UserTokensRepository( private val storageService: UserTokensStorageService, @@ -37,26 +38,14 @@ class UserTokensRepository( return@withContext loadTokensOffline(card, userId) } - runCatching { tangemTechApi.getUserTokens(userId) } - .onSuccess { response -> - return@withContext response.tokens - .mapNotNull(Currency.Companion::fromTokenResponse).also { - storageService.saveUserTokens(userId, it.toUserTokensResponse()) - } - .distinct() - } - .onFailure { - return@withContext handleGetUserTokensFailure(card = card, userId = userId, error = it) - } - - error("Unreachable code because runCatching must return result") + return@withContext remoteGetUserTokens(card, userId) } // TODO("After adding DI") replace with CoroutineDispatcherProvider suspend fun saveUserTokens(card: CardDTO, tokens: List) = withContext(dispatchers.io) { val userId = getUserWalletId(card) ?: return@withContext val userTokens = tokens.toUserTokensResponse() - tangemTechApi.saveUserTokens(userId, userTokens) + remoteSaveUserTokens(userId, userTokens) storageService.saveUserTokens(userId, userTokens) } @@ -97,7 +86,7 @@ class UserTokensRepository( return when { error is TangemSdkError.NetworkError && error.customMessage.contains(NOT_FOUND_HTTP_CODE) -> storageService.getUserTokens(card).also { - tangemTechApi.saveUserTokens(userId = userId, userTokens = it.toUserTokensResponse()) + remoteSaveUserTokens(userId = userId, userTokens = it.toUserTokensResponse()) } else -> { val tokens = storageService.getUserTokens(userId) ?: storageService.getUserTokens(card) @@ -106,6 +95,31 @@ class UserTokensRepository( } } + private suspend fun remoteGetUserTokens(card: CardDTO, userId: String): List { + runCatching { + tangemTechApi.getUserTokens(userId) + }.onSuccess { response -> + return response.tokens + .mapNotNull(Currency.Companion::fromTokenResponse).also { + storageService.saveUserTokens(userId, it.toUserTokensResponse()) + } + .distinct() + }.onFailure { + return handleGetUserTokensFailure(card = card, userId = userId, error = it) + } + error("Unreachable code because runCatching must return result") + } + + private suspend fun remoteSaveUserTokens(userId: String, userTokens: UserTokensResponse) { + // it can throw okhttp3.internal.http2.StreamResetException: stream was reset: INTERNAL_ERROR + // if the /user-tokens endpoint disabled + runCatching { + tangemTechApi.saveUserTokens(userId, userTokens) + }.onFailure { + Timber.e(it) + } + } + private fun getUserWalletId(card: CardDTO): String? { return UserWalletIdBuilder.card(card).build() ?.stringValue