diff --git a/app/build.gradle.kts b/app/build.gradle.kts index e371e05751..23d6214b10 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -80,6 +80,7 @@ dependencies { implementation(projects.features.wallet.impl) implementation(projects.features.tokendetails.api) implementation(projects.features.tokendetails.impl) + implementation(projects.features.send.api) /** AndroidX libraries */ implementation(deps.androidx.core.ktx) diff --git a/app/src/main/java/com/tangem/tap/di/ActivityModule.kt b/app/src/main/java/com/tangem/tap/di/ActivityModule.kt index 67e752053c..2ff65f28a2 100644 --- a/app/src/main/java/com/tangem/tap/di/ActivityModule.kt +++ b/app/src/main/java/com/tangem/tap/di/ActivityModule.kt @@ -13,6 +13,9 @@ import dagger.Provides import dagger.hilt.InstallIn import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.components.SingletonComponent +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.SupervisorJob import javax.inject.Singleton @Module @@ -47,4 +50,11 @@ internal object ActivityModule { fun provideDefaultRampManager(appStateHolder: AppStateHolder): RampStateManager { return DefaultRampManager(appStateHolder.exchangeService) } + + @Provides + @Singleton + @DelayedWork + fun provideActivityDelayedWorkCoroutineScope(): CoroutineScope { + return CoroutineScope(SupervisorJob() + Dispatchers.IO) + } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/di/Qualifiers.kt b/app/src/main/java/com/tangem/tap/di/Qualifiers.kt new file mode 100644 index 0000000000..df7fab7779 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/di/Qualifiers.kt @@ -0,0 +1,8 @@ +@file:Suppress("Filename") +package com.tangem.tap.di + +import javax.inject.Qualifier + +@Qualifier +@Retention(AnnotationRetention.BINARY) +annotation class DelayedWork \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/di/domain/TokensDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/TokensDomainModule.kt index b2f6fa50eb..9bbf1229fa 100644 --- a/app/src/main/java/com/tangem/tap/di/domain/TokensDomainModule.kt +++ b/app/src/main/java/com/tangem/tap/di/domain/TokensDomainModule.kt @@ -173,4 +173,14 @@ internal object TokensDomainModule { currenciesRepository = currenciesRepository, ) } + + @Provides + @ViewModelScoped + fun provideUpdateDelayedCurrencyStatusUseCase( + networksRepository: NetworksRepository, + ): UpdateDelayedNetworkStatusUseCase { + return UpdateDelayedNetworkStatusUseCase( + networksRepository = networksRepository, + ) + } } \ No newline at end of file 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 eeb7c0cf20..4e0490a4f9 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 @@ -270,9 +270,7 @@ private fun sendTransaction( dispatch(NavigationAction.PopBackTo()) } scope.launch(Dispatchers.IO) { - updateWallet(walletManager) - delay(timeMillis = 11000) // more than 10000 to avoid throttling - updateWallet(walletManager) + updateAfterTransaction(walletManager) } } is SimpleResult.Failure -> { @@ -414,6 +412,19 @@ private fun updateWarnings(dispatch: (Action) -> Unit) { dispatch(SendAction.Warnings.Set(warnings)) } +private suspend fun updateAfterTransaction(walletManager: WalletManager) { + val walletFeatureToggles = store.state.daggerGraphState.get(DaggerGraphState::walletFeatureToggles) + if (!walletFeatureToggles.isRedesignedScreenEnabled) { + updateWalletsLegacy(walletManager) + } +} + +private suspend fun updateWalletsLegacy(walletManager: WalletManager) { + updateWallet(walletManager) + delay(timeMillis = 11000) // more than 10000 to avoid throttling + updateWallet(walletManager) +} + private suspend fun updateWallet(walletManager: WalletManager) { val selectedUserWallet = userWalletsListManager.selectedUserWalletSync.guard { Timber.e("Unable to update wallet, no user wallet selected") diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/reducers/SendScreenReducer.kt b/app/src/main/java/com/tangem/tap/features/send/redux/reducers/SendScreenReducer.kt index 8a9cab2329..f2d05ab0c2 100644 --- a/app/src/main/java/com/tangem/tap/features/send/redux/reducers/SendScreenReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/send/redux/reducers/SendScreenReducer.kt @@ -53,6 +53,7 @@ private class SendReducer : SendInternalReducer { is SendAction.Dialog.Hide -> sendState.copy(dialog = null) is SendAction.Warnings.Set -> sendState.copy(sendWarningsList = action.warningList) is SendAction.SendSpecificTransaction -> handleSendSpecificTransactionAction(action, sendState) + is SendAction.SendSuccess -> sendState.copy(isSuccessSend = true) else -> return sendState } diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/states/SendState.kt b/app/src/main/java/com/tangem/tap/features/send/redux/states/SendState.kt index fa9aa1d60e..34553ec931 100644 --- a/app/src/main/java/com/tangem/tap/features/send/redux/states/SendState.kt +++ b/app/src/main/java/com/tangem/tap/features/send/redux/states/SendState.kt @@ -43,6 +43,7 @@ data class SendState( val sendButtonState: IndeterminateProgressButton = IndeterminateProgressButton(ButtonState.DISABLED), val dialog: StateDialog? = null, val externalTransactionData: ExternalTransactionData? = null, + val isSuccessSend: Boolean = false, ) : SendScreenState { override val stateId: StateId = StateId.SEND_SCREEN diff --git a/app/src/main/java/com/tangem/tap/features/send/ui/SendFragment.kt b/app/src/main/java/com/tangem/tap/features/send/ui/SendFragment.kt index 5da7c2fe6a..e115eff66d 100644 --- a/app/src/main/java/com/tangem/tap/features/send/ui/SendFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/send/ui/SendFragment.kt @@ -76,6 +76,7 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) lifecycle.addObserver(viewModel) + sendSubscriber.initViewModel(viewModel) Analytics.send(Token.Send.ScreenOpened()) } diff --git a/app/src/main/java/com/tangem/tap/features/send/ui/SendViewModel.kt b/app/src/main/java/com/tangem/tap/features/send/ui/SendViewModel.kt index 1ac28bda46..245d0f9a89 100644 --- a/app/src/main/java/com/tangem/tap/features/send/ui/SendViewModel.kt +++ b/app/src/main/java/com/tangem/tap/features/send/ui/SendViewModel.kt @@ -3,23 +3,39 @@ package com.tangem.tap.features.send.ui import androidx.lifecycle.* import com.tangem.domain.balancehiding.IsBalanceHiddenUseCase import com.tangem.domain.balancehiding.ListenToFlipsUseCase +import com.tangem.domain.tokens.UpdateDelayedNetworkStatusUseCase +import com.tangem.domain.tokens.model.CryptoCurrency +import com.tangem.domain.wallets.usecase.GetSelectedWalletUseCase +import com.tangem.features.send.navigation.SendRouter +import com.tangem.tap.di.DelayedWork import com.tangem.tap.features.send.redux.AmountAction import com.tangem.tap.proxy.AppStateHolder import com.tangem.utils.coroutines.CoroutineDispatcherProvider import dagger.hilt.android.lifecycle.HiltViewModel -import kotlinx.coroutines.flow.* +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.collect +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch import kotlinx.coroutines.withContext +import timber.log.Timber import javax.inject.Inject +@Suppress("LongParameterList") @HiltViewModel internal class SendViewModel @Inject constructor( private val dispatchers: CoroutineDispatcherProvider, private val appStateHolder: AppStateHolder, private val isBalanceHiddenUseCase: IsBalanceHiddenUseCase, private val listenToFlipsUseCase: ListenToFlipsUseCase, + private val updateDelayedCurrencyStatusUseCase: UpdateDelayedNetworkStatusUseCase, + private val getSelectedWalletUseCase: GetSelectedWalletUseCase, + @DelayedWork private val coroutineScope: CoroutineScope, + savedStateHandle: SavedStateHandle, ) : ViewModel(), DefaultLifecycleObserver { + private val cryptoCurrency: CryptoCurrency? = savedStateHandle[SendRouter.CRYPTO_CURRENCY_KEY] + override fun onCreate(owner: LifecycleOwner) { isBalanceHiddenUseCase() .flowWithLifecycle(owner.lifecycle) @@ -36,4 +52,24 @@ internal class SendViewModel @Inject constructor( .collect() } } + + fun updateCurrencyDelayed() { + if (cryptoCurrency != null) { + coroutineScope.launch { + getSelectedWalletUseCase() + .fold( + ifLeft = { Timber.e(it.toString()) }, + ifRight = { wallet -> + updateDelayedCurrencyStatusUseCase(wallet.walletId, cryptoCurrency.network, true) + }, + ) + } + } else { + Timber.w("$TAG: cryptoCurrency is null, legacy flow") + } + } + + companion object { + private const val TAG = "SendViewModel" + } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/send/ui/stateSubscribers/SendStateSubscriber.kt b/app/src/main/java/com/tangem/tap/features/send/ui/stateSubscribers/SendStateSubscriber.kt index 1e44546acc..d71adc7cc8 100644 --- a/app/src/main/java/com/tangem/tap/features/send/ui/stateSubscribers/SendStateSubscriber.kt +++ b/app/src/main/java/com/tangem/tap/features/send/ui/stateSubscribers/SendStateSubscriber.kt @@ -18,6 +18,7 @@ import com.tangem.tap.features.send.redux.SendAction import com.tangem.tap.features.send.redux.states.* import com.tangem.tap.features.send.ui.FeeUiHelper import com.tangem.tap.features.send.ui.SendFragment +import com.tangem.tap.features.send.ui.SendViewModel import com.tangem.tap.features.send.ui.dialogs.* import com.tangem.tap.features.wallet.redux.ProgressState import com.tangem.tap.features.wallet.redux.utils.ROUGH_SIGN @@ -29,13 +30,23 @@ import com.tangem.wallet.R [REDACTED_AUTHOR] */ @Suppress("LargeClass") -class SendStateSubscriber(fragment: BaseStoreFragment) : FragmentStateSubscriber(fragment) { +internal class SendStateSubscriber( + fragment: BaseStoreFragment, +) : FragmentStateSubscriber(fragment) { private var dialog: Dialog? = null + private var sendViewModel: SendViewModel? = null + fun initViewModel(viewModel: SendViewModel) { + sendViewModel = viewModel + } override fun updateWithNewState(fg: BaseStoreFragment, state: SendState) { fg.view ?: return if (fg !is SendFragment) return + if (state.isSuccessSend) { + sendViewModel?.updateCurrencyDelayed() + return + } val lastChangedStates = state.lastChangedStates.toList() state.lastChangedStates.clear() diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/TradeCryptoMiddleware.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/TradeCryptoMiddleware.kt index 36ab93bff5..666f1ac84e 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/TradeCryptoMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/TradeCryptoMiddleware.kt @@ -15,6 +15,7 @@ import com.tangem.domain.tokens.legacy.TradeCryptoAction import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.tokens.model.Network import com.tangem.feature.swap.presentation.SwapFragment +import com.tangem.features.send.navigation.SendRouter import com.tangem.tap.common.analytics.events.AnalyticsParam import com.tangem.tap.common.analytics.events.Token import com.tangem.tap.common.apptheme.MutableAppThemeModeHolder @@ -365,8 +366,8 @@ class TradeCryptoMiddleware { ) } } - - store.dispatchOnMain(NavigationAction.NavigateTo(AppScreen.Send)) + val bundle = bundleOf(SendRouter.CRYPTO_CURRENCY_KEY to currency) + store.dispatchOnMain(NavigationAction.NavigateTo(screen = AppScreen.Send, bundle = bundle)) } } @@ -415,7 +416,8 @@ class TradeCryptoMiddleware { is CryptoCurrency.Token -> error("Action.tokenStatus.currency is Token") } - store.dispatchOnMain(NavigationAction.NavigateTo(AppScreen.Send)) + val bundle = bundleOf(SendRouter.CRYPTO_CURRENCY_KEY to currency) + store.dispatchOnMain(NavigationAction.NavigateTo(screen = AppScreen.Send, bundle = bundle)) } } } \ No newline at end of file diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/UpdateDelayedNetworkStatusUseCase.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/UpdateDelayedNetworkStatusUseCase.kt new file mode 100644 index 0000000000..30ff71c68f --- /dev/null +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/UpdateDelayedNetworkStatusUseCase.kt @@ -0,0 +1,59 @@ +package com.tangem.domain.tokens + +import arrow.core.Either +import arrow.core.raise.Raise +import arrow.core.raise.catch +import arrow.core.raise.either +import com.tangem.domain.tokens.error.CurrencyStatusError +import com.tangem.domain.tokens.model.Network +import com.tangem.domain.tokens.repository.NetworksRepository +import com.tangem.domain.wallets.models.UserWalletId +import kotlinx.coroutines.delay + +/** + * Use case responsible for fetching currency status information, including network status + * and quotes for a given cryptocurrency. It provides methods to fetch currency status either + * by providing a specific currency ID or fetching the status of the primary currency. + * + * @param networksRepository The repository for retrieving network-related data. + */ + +class UpdateDelayedNetworkStatusUseCase( + private val networksRepository: NetworksRepository, +) { + + /** + * Fetches the status of a specific cryptocurrency for a given user wallet. + * + * @param userWalletId The ID of the user's wallet. + * @param network Network of the cryptocurrency. + * @param refresh Indicates whether to force a refresh of the status data. + * @return An [Either] representing success (Right) or an error (Left) in fetching the status. + */ + suspend operator fun invoke( + userWalletId: UserWalletId, + network: Network, + refresh: Boolean = false, + ): Either { + delay(DELAY_MILLIS) + return either { + fetchNetworkStatus(userWalletId, network, refresh) + } + } + + private suspend fun Raise.fetchNetworkStatus( + userWalletId: UserWalletId, + network: Network, + refresh: Boolean, + ) { + catch( + block = { networksRepository.getNetworkStatusesSync(userWalletId, setOf(network), refresh) }, + ) { + raise(CurrencyStatusError.DataError(it)) + } + } + + companion object { + private const val DELAY_MILLIS = 11000L + } +} \ No newline at end of file diff --git a/features/send/api/.gitignore b/features/send/api/.gitignore new file mode 100644 index 0000000000..796b96d1c4 --- /dev/null +++ b/features/send/api/.gitignore @@ -0,0 +1 @@ +/build diff --git a/features/send/api/build.gradle.kts b/features/send/api/build.gradle.kts new file mode 100644 index 0000000000..6b742b5542 --- /dev/null +++ b/features/send/api/build.gradle.kts @@ -0,0 +1,17 @@ +plugins { + alias(deps.plugins.android.library) + alias(deps.plugins.kotlin.android) + id("kotlin-parcelize") + id("configuration") +} + +android { + namespace = "com.tangem.features.send.api" +} + +dependencies { + implementation(projects.domain.tokens.models) + + /** AndroidX */ + implementation(deps.androidx.fragment.ktx) +} \ No newline at end of file diff --git a/features/send/api/src/main/kotlin/com/tangem/features/send/navigation/SendRouter.kt b/features/send/api/src/main/kotlin/com/tangem/features/send/navigation/SendRouter.kt new file mode 100644 index 0000000000..0d0676b23b --- /dev/null +++ b/features/send/api/src/main/kotlin/com/tangem/features/send/navigation/SendRouter.kt @@ -0,0 +1,12 @@ +package com.tangem.features.send.navigation + +import androidx.fragment.app.Fragment + +interface SendRouter { + + fun getEntryFragment(): Fragment + + companion object { + const val CRYPTO_CURRENCY_KEY = "send_crypto_currency" + } +} \ No newline at end of file diff --git a/features/tokendetails/impl/build.gradle.kts b/features/tokendetails/impl/build.gradle.kts index 97561c5bb1..bc68543a4d 100644 --- a/features/tokendetails/impl/build.gradle.kts +++ b/features/tokendetails/impl/build.gradle.kts @@ -66,4 +66,5 @@ dependencies { /** Feature Apis */ implementation(projects.features.tokendetails.api) + implementation(projects.features.send.api) } \ No newline at end of file diff --git a/features/wallet/impl/build.gradle.kts b/features/wallet/impl/build.gradle.kts index 6edd0059b2..b48335e242 100644 --- a/features/wallet/impl/build.gradle.kts +++ b/features/wallet/impl/build.gradle.kts @@ -69,4 +69,5 @@ dependencies { /** Feature Apis */ implementation(projects.features.wallet.api) implementation(projects.features.tokendetails.api) + implementation(projects.features.send.api) } \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index 00b22db5d1..174e29f0e4 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -91,6 +91,8 @@ include(":features:tokendetails:impl") include(":features:learn2earn:api") include(":features:learn2earn:impl") + +include(":features:send:api") // endregion Feature modules // region Domain modules