From 25bae0ed98f435b45f93030abc16542a35882db8 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 7 Nov 2024 13:40:46 +0400 Subject: [PATCH] Updated on 2026-08-14 --- .../buttons/actions/ActionButtonConfig.kt | 1 + .../ui/components/buttons/actions/Actions.kt | 53 +++++++++++++-- .../implementors/MultiWalletContentLoader.kt | 17 ++++- .../MultiWalletContentLoaderFactory.kt | 6 ++ .../wallet/state/model/WalletManageButton.kt | 10 ++- .../UpdateMultiWalletActionsTransformer.kt | 64 +++++++++++++++++++ .../state/utils/WalletLoadingStateFactory.kt | 24 +++++-- .../MultiWalletActionsSubscriber.kt | 52 +++++++++++++++ .../viewmodels/intents/WalletClickIntents.kt | 10 +++ .../WalletCurrencyActionsClickIntents.kt | 21 ++++++ 10 files changed, 243 insertions(+), 15 deletions(-) create mode 100644 features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/UpdateMultiWalletActionsTransformer.kt create mode 100644 features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/MultiWalletActionsSubscriber.kt diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/buttons/actions/ActionButtonConfig.kt b/core/ui/src/main/java/com/tangem/core/ui/components/buttons/actions/ActionButtonConfig.kt index c93d92643e..d315a2a715 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/buttons/actions/ActionButtonConfig.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/buttons/actions/ActionButtonConfig.kt @@ -23,4 +23,5 @@ data class ActionButtonConfig( val onLongClick: (() -> TextReference?)? = null, val enabled: Boolean = true, val dimContent: Boolean = false, + val isInProgress: Boolean = false, ) \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/buttons/actions/Actions.kt b/core/ui/src/main/java/com/tangem/core/ui/components/buttons/actions/Actions.kt index 60bc6e7e12..728eb3a49c 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/buttons/actions/Actions.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/buttons/actions/Actions.kt @@ -8,8 +8,9 @@ import androidx.compose.foundation.background import androidx.compose.foundation.combinedClickable import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material.Icon -import androidx.compose.material.Text +import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.Icon +import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment @@ -77,7 +78,6 @@ fun ActionButton( ) } -@OptIn(ExperimentalFoundationApi::class) @Composable private fun Button( config: ActionButtonConfig, @@ -89,12 +89,33 @@ private fun Button( targetValue = if (config.enabled) color else TangemTheme.colors.button.disabled, label = "Update background color", ) - val context = LocalContext.current - Row( + + Box( modifier = modifier .heightIn(min = TangemTheme.dimens.size36) .clip(shape) - .background(color = backgroundColor) + .background(color = backgroundColor), + ) { + Content(config = config) + + if (config.isInProgress) { + Loading( + backgroundColor = backgroundColor, + modifier = Modifier + .matchParentSize() + .align(alignment = Alignment.Center), + ) + } + } +} + +@OptIn(ExperimentalFoundationApi::class) +@Composable +private fun Content(config: ActionButtonConfig) { + val context = LocalContext.current + + Row( + modifier = Modifier .combinedClickable( enabled = config.enabled, onClick = config.onClick, @@ -149,6 +170,19 @@ private fun Button( } } +@Composable +private fun Loading(backgroundColor: Color, modifier: Modifier = Modifier) { + Box( + modifier = modifier.background(color = backgroundColor), + contentAlignment = Alignment.Center, + ) { + CircularProgressIndicator( + modifier = Modifier.size(TangemTheme.dimens.size24), + color = TangemTheme.colors.icon.accent, + ) + } +} + @Preview(group = "RoundedActionButton", showBackground = true) @Preview(group = "RoundedActionButton", showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES) @Composable @@ -188,5 +222,12 @@ private class ActionStateProvider : CollectionPreviewParameterProvider { - return listOf( + return listOfNotNull( MultiWalletTokenListSubscriber( userWallet = userWallet, stateHolder = stateHolder, @@ -50,6 +55,16 @@ internal class MultiWalletContentLoader( getMultiWalletWarningsFactory = getMultiWalletWarningsFactory, walletWarningsAnalyticsSender = walletWarningsAnalyticsSender, ), + createMultiWalletButtonsSubscriber(), ) } + + private fun createMultiWalletButtonsSubscriber(): MultiWalletActionsSubscriber? { + return MultiWalletActionsSubscriber( + userWallet = userWallet, + rampStateManager = rampStateManager, + stateController = stateHolder, + ) + .takeIf { walletFeatureToggles.isMainActionButtonsEnabled } + } } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/MultiWalletContentLoaderFactory.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/MultiWalletContentLoaderFactory.kt index 7a70b865b2..5bc11a8b00 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/MultiWalletContentLoaderFactory.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/MultiWalletContentLoaderFactory.kt @@ -1,6 +1,7 @@ package com.tangem.feature.wallet.presentation.wallet.loaders.implementors import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase +import com.tangem.domain.exchange.RampStateManager import com.tangem.domain.tokens.ApplyTokenListSortingUseCase import com.tangem.domain.tokens.RunPolkadotAccountHealthCheckUseCase import com.tangem.domain.wallets.models.UserWallet @@ -11,6 +12,7 @@ import com.tangem.feature.wallet.presentation.wallet.domain.MultiWalletTokenList import com.tangem.feature.wallet.presentation.wallet.domain.WalletWithFundsChecker import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntents +import com.tangem.features.wallet.featuretoggles.WalletFeatureToggles import dagger.hilt.android.scopes.ViewModelScoped import javax.inject.Inject @@ -26,6 +28,8 @@ internal class MultiWalletContentLoaderFactory @Inject constructor( private val applyTokenListSortingUseCase: ApplyTokenListSortingUseCase, private val walletWarningsAnalyticsSender: WalletWarningsAnalyticsSender, private val runPolkadotAccountHealthCheckUseCase: RunPolkadotAccountHealthCheckUseCase, + private val rampStateManager: RampStateManager, + private val walletFeatureToggles: WalletFeatureToggles, ) { fun create(userWallet: UserWallet, clickIntents: WalletClickIntents): WalletContentLoader { @@ -41,6 +45,8 @@ internal class MultiWalletContentLoaderFactory @Inject constructor( walletWarningsAnalyticsSender = walletWarningsAnalyticsSender, applyTokenListSortingUseCase = applyTokenListSortingUseCase, runPolkadotAccountHealthCheckUseCase = runPolkadotAccountHealthCheckUseCase, + rampStateManager = rampStateManager, + walletFeatureToggles = walletFeatureToggles, ) } } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletManageButton.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletManageButton.kt index 0d9c5af5dd..515041aec5 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletManageButton.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletManageButton.kt @@ -38,6 +38,7 @@ internal sealed class WalletManageButton(val config: ActionButtonConfig) { override val enabled: Boolean, override val dimContent: Boolean, override val onClick: () -> Unit, + val isInProgress: Boolean = false, ) : WalletManageButton( config = ActionButtonConfig( @@ -46,6 +47,7 @@ internal sealed class WalletManageButton(val config: ActionButtonConfig) { onClick = onClick, enabled = enabled, dimContent = dimContent, + isInProgress = isInProgress, ), ) @@ -85,10 +87,10 @@ internal sealed class WalletManageButton(val config: ActionButtonConfig) { config = ActionButtonConfig( text = TextReference.Res(id = R.string.common_receive), iconResId = R.drawable.ic_arrow_down_24, - onClick = onClick, - onLongClick = onLongClick, enabled = enabled, dimContent = dimContent, + onClick = onClick, + onLongClick = onLongClick, ), ) @@ -117,6 +119,7 @@ internal sealed class WalletManageButton(val config: ActionButtonConfig) { override val enabled: Boolean, override val dimContent: Boolean, override val onClick: () -> Unit, + val isInProgress: Boolean = false, ) : WalletManageButton( config = ActionButtonConfig( text = TextReference.Res(id = R.string.common_sell), @@ -124,6 +127,7 @@ internal sealed class WalletManageButton(val config: ActionButtonConfig) { onClick = onClick, enabled = enabled, dimContent = dimContent, + isInProgress = isInProgress, ), ) @@ -138,6 +142,7 @@ internal sealed class WalletManageButton(val config: ActionButtonConfig) { override val enabled: Boolean, override val dimContent: Boolean, override val onClick: () -> Unit, + val isInProgress: Boolean = false, ) : WalletManageButton( config = ActionButtonConfig( text = TextReference.Res(id = R.string.swapping_swap_action), @@ -145,6 +150,7 @@ internal sealed class WalletManageButton(val config: ActionButtonConfig) { onClick = onClick, enabled = enabled, dimContent = dimContent, + isInProgress = isInProgress, ), ) } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/UpdateMultiWalletActionsTransformer.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/UpdateMultiWalletActionsTransformer.kt new file mode 100644 index 0000000000..a59a4bf108 --- /dev/null +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/UpdateMultiWalletActionsTransformer.kt @@ -0,0 +1,64 @@ +package com.tangem.feature.wallet.presentation.wallet.state.transformers + +import com.tangem.domain.core.lce.Lce +import com.tangem.domain.wallets.models.UserWalletId +import com.tangem.feature.wallet.presentation.wallet.state.model.WalletManageButton +import com.tangem.feature.wallet.presentation.wallet.state.model.WalletState +import kotlinx.collections.immutable.PersistentList +import kotlinx.collections.immutable.toPersistentList +import timber.log.Timber + +internal class UpdateMultiWalletActionsTransformer( + userWalletId: UserWalletId, + private val buyStatus: Lce, + private val sellStatus: Lce, + private val swapStatus: Lce, +) : WalletStateTransformer(userWalletId = userWalletId) { + + override fun transform(prevState: WalletState): WalletState { + return when (prevState) { + is WalletState.MultiCurrency.Content -> { + prevState.copy(buttons = prevState.buttons.updateButtons()) + } + is WalletState.MultiCurrency.Locked -> { + Timber.w("Impossible to load primary currency status for locked wallet") + prevState + } + is WalletState.SingleCurrency -> { + Timber.w("Impossible to load crypto currency actions for multi-currency wallet") + prevState + } + is WalletState.Visa -> { + Timber.w("Impossible to load crypto currency actions for VISA wallet") + prevState + } + } + } + + private fun PersistentList.updateButtons(): PersistentList { + return map { + when (it) { + is WalletManageButton.Buy -> { + it.copy( + enabled = buyStatus.isContent(), + dimContent = !buyStatus.isContent(), + ) + } + is WalletManageButton.Sell -> { + it.copy( + enabled = sellStatus.isContent(), + dimContent = !sellStatus.isContent(), + ) + } + is WalletManageButton.Swap -> { + it.copy( + enabled = swapStatus.isContent(), + dimContent = !swapStatus.isContent(), + ) + } + else -> it + } + } + .toPersistentList() + } +} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/utils/WalletLoadingStateFactory.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/utils/WalletLoadingStateFactory.kt index 2b622d7571..6b9dda9803 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/utils/WalletLoadingStateFactory.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/utils/WalletLoadingStateFactory.kt @@ -37,7 +37,7 @@ internal class WalletLoadingStateFactory( return WalletState.MultiCurrency.Content( pullToRefreshConfig = createPullToRefreshConfig(), walletCardState = userWallet.toLoadingWalletCardState(), - buttons = createMultiWalletActions(), + buttons = createMultiWalletActions(userWallet), warnings = persistentListOf(), bottomSheetConfig = null, tokensListState = WalletTokensListState.ContentState.Loading, @@ -65,7 +65,7 @@ internal class WalletLoadingStateFactory( return WalletState.Visa.Content( pullToRefreshConfig = createPullToRefreshConfig(), walletCardState = userWallet.toLoadingWalletCardState(), - buttons = createMultiWalletActions(), + buttons = createMultiWalletActions(userWallet), warnings = persistentListOf(), bottomSheetConfig = null, balancesAndLimitBlockState = BalancesAndLimitsBlockState.Loading, @@ -93,13 +93,25 @@ internal class WalletLoadingStateFactory( ) } - private fun createMultiWalletActions(): PersistentList { + private fun createMultiWalletActions(userWallet: UserWallet): PersistentList { if (!walletFeatureToggles.isMainActionButtonsEnabled) return persistentListOf() return persistentListOf( - WalletManageButton.Buy(enabled = false, dimContent = true, onClick = { }), - WalletManageButton.Swap(enabled = false, dimContent = true, onClick = { }), - WalletManageButton.Sell(enabled = false, dimContent = true, onClick = { }), + WalletManageButton.Buy( + enabled = false, + dimContent = true, + onClick = { clickIntents.onMultiWalletBuyClick(userWalletId = userWallet.walletId) }, + ), + WalletManageButton.Swap( + enabled = false, + dimContent = true, + onClick = { clickIntents.onMultiWalletSwapClick(userWalletId = userWallet.walletId) }, + ), + WalletManageButton.Sell( + enabled = false, + dimContent = true, + onClick = { clickIntents.onMultiWalletSellClick(userWalletId = userWallet.walletId) }, + ), ) } diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/MultiWalletActionsSubscriber.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/MultiWalletActionsSubscriber.kt new file mode 100644 index 0000000000..266bb7b67c --- /dev/null +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/MultiWalletActionsSubscriber.kt @@ -0,0 +1,52 @@ +package com.tangem.feature.wallet.presentation.wallet.subscribers + +import com.tangem.domain.core.lce.Lce +import com.tangem.domain.exchange.RampStateManager +import com.tangem.domain.wallets.models.UserWallet +import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController +import com.tangem.feature.wallet.presentation.wallet.state.transformers.UpdateMultiWalletActionsTransformer +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.onEach + +/** + * Multi-currency wallet subscriber for actions state updating + * + * @property userWallet user wallet + * @property rampStateManager ramp state manager + * @property stateController state controller + * +[REDACTED_AUTHOR] + */ +internal class MultiWalletActionsSubscriber( + private val userWallet: UserWallet, + private val rampStateManager: RampStateManager, + private val stateController: WalletStateController, +) : WalletSubscriber() { + + override fun create(coroutineScope: CoroutineScope): Flow<*> { + return combine( + flow = rampStateManager.getBuyInitializationStatus(), + flow2 = rampStateManager.getSellInitializationStatus(), + flow3 = rampStateManager.getSwapInitializationStatus(userWalletId = userWallet.walletId), + transform = ::RampStatuses, + ) + .onEach { statuses -> + stateController.update( + UpdateMultiWalletActionsTransformer( + userWalletId = userWallet.walletId, + buyStatus = statuses.buy, + sellStatus = statuses.sell, + swapStatus = statuses.swap, + ), + ) + } + } + + private data class RampStatuses( + val buy: Lce, + val sell: Lce, + val swap: Lce, + ) +} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletClickIntents.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletClickIntents.kt index 0c716472a9..f40ea9b059 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletClickIntents.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletClickIntents.kt @@ -3,6 +3,7 @@ package com.tangem.feature.wallet.presentation.wallet.viewmodels.intents import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase import com.tangem.domain.appcurrency.extenstions.unwrap import com.tangem.domain.common.util.cardTypesResolver +import com.tangem.domain.exchange.RampStateManager import com.tangem.domain.settings.NeverToShowWalletsScrollPreview import com.tangem.domain.tokens.FetchCardTokenListUseCase import com.tangem.domain.tokens.FetchCurrencyStatusUseCase @@ -20,6 +21,8 @@ import com.tangem.feature.wallet.presentation.wallet.state.transformers.SetToken import com.tangem.utils.coroutines.CoroutineDispatcherProvider import dagger.hilt.android.scopes.ViewModelScoped import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.async +import kotlinx.coroutines.awaitAll import kotlinx.coroutines.launch import javax.inject.Inject @@ -41,6 +44,7 @@ internal class WalletClickIntents @Inject constructor( private val fetchCurrencyStatusUseCase: FetchCurrencyStatusUseCase, private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase, private val neverToShowWalletsScrollPreview: NeverToShowWalletsScrollPreview, + private val rampStateManager: RampStateManager, private val dispatchers: CoroutineDispatcherProvider, ) : BaseWalletClickIntents(), WalletCardClickIntents by walletCardClickIntentsImplementor, @@ -116,6 +120,12 @@ internal class WalletClickIntents @Inject constructor( fetchTokenListUseCase(userWalletId = userWallet.walletId, mode = RefreshMode.FULL) } + listOf( + async { rampStateManager.fetchBuyServiceData() }, + async { rampStateManager.fetchSellServiceData() }, + ) + .awaitAll() + maybeFetchResult.onLeft { stateHolder.update( SetTokenListErrorTransformer( diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletCurrencyActionsClickIntents.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletCurrencyActionsClickIntents.kt index 21fcf81bf1..dba1cd4f91 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletCurrencyActionsClickIntents.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletCurrencyActionsClickIntents.kt @@ -81,6 +81,12 @@ interface WalletCurrencyActionsClickIntents { fun onExploreClick() fun onAnalyticsClick(cryptoCurrencyStatus: CryptoCurrencyStatus) + + fun onMultiWalletBuyClick(userWalletId: UserWalletId) + + fun onMultiWalletSellClick(userWalletId: UserWalletId) + + fun onMultiWalletSwapClick(userWalletId: UserWalletId) } @Suppress("LongParameterList", "LargeClass") @@ -432,6 +438,21 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor( } } + override fun onMultiWalletSellClick(userWalletId: UserWalletId) { + // TODO [REDACTED_JIRA] + appRouter.push(route = AppRoute.SellCrypto(userWalletId = userWalletId)) + } + + override fun onMultiWalletSwapClick(userWalletId: UserWalletId) { + // TODO [REDACTED_JIRA] + // appRouter.push(route = AppRoute.SwapCrypto(userWalletId = userWalletId)) + } + + override fun onMultiWalletBuyClick(userWalletId: UserWalletId) { + // TODO [REDACTED_JIRA] + appRouter.push(route = AppRoute.BuyCrypto(userWalletId = userWalletId)) + } + private fun openExplorer() { val userWalletId = stateHolder.getSelectedWalletId()