Updated on 2026-08-14

This commit is contained in:
Tangem 2024-11-07 13:40:46 +04:00
parent cd9e96c526
commit 25bae0ed98
10 changed files with 243 additions and 15 deletions

View file

@ -23,4 +23,5 @@ data class ActionButtonConfig(
val onLongClick: (() -> TextReference?)? = null,
val enabled: Boolean = true,
val dimContent: Boolean = false,
val isInProgress: Boolean = false,
)

View file

@ -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<ActionBut
enabled = false,
onClick = {},
),
ActionButtonConfig(
text = TextReference.Str(value = "Loading"),
iconResId = R.drawable.ic_arrow_down_24,
enabled = false,
onClick = {},
isInProgress = true,
),
),
)

View file

@ -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
@ -10,10 +11,12 @@ import com.tangem.feature.wallet.presentation.wallet.domain.GetMultiWalletWarnin
import com.tangem.feature.wallet.presentation.wallet.domain.MultiWalletTokenListStore
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.subscribers.MultiWalletActionsSubscriber
import com.tangem.feature.wallet.presentation.wallet.subscribers.MultiWalletTokenListSubscriber
import com.tangem.feature.wallet.presentation.wallet.subscribers.MultiWalletWarningsSubscriber
import com.tangem.feature.wallet.presentation.wallet.subscribers.WalletSubscriber
import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntents
import com.tangem.features.wallet.featuretoggles.WalletFeatureToggles
@Suppress("LongParameterList")
internal class MultiWalletContentLoader(
@ -28,10 +31,12 @@ internal class MultiWalletContentLoader(
private val applyTokenListSortingUseCase: ApplyTokenListSortingUseCase,
private val getMultiWalletWarningsFactory: GetMultiWalletWarningsFactory,
private val runPolkadotAccountHealthCheckUseCase: RunPolkadotAccountHealthCheckUseCase,
private val rampStateManager: RampStateManager,
private val walletFeatureToggles: WalletFeatureToggles,
) : WalletContentLoader(id = userWallet.walletId) {
override fun create(): List<WalletSubscriber> {
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 }
}
}

View file

@ -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,
)
}
}

View file

@ -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,
),
)
}

View file

@ -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<Throwable, Any>,
private val sellStatus: Lce<Throwable, Any>,
private val swapStatus: Lce<Throwable, Any>,
) : 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<WalletManageButton>.updateButtons(): PersistentList<WalletManageButton> {
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()
}
}

View file

@ -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<WalletManageButton> {
private fun createMultiWalletActions(userWallet: UserWallet): PersistentList<WalletManageButton> {
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) },
),
)
}

View file

@ -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<Throwable, Any>,
val sell: Lce<Throwable, Any>,
val swap: Lce<Throwable, Any>,
)
}

View file

@ -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(

View file

@ -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()