Updated on 2026-08-14
This commit is contained in:
commit
652159dbce
316 changed files with 6242 additions and 3084 deletions
|
|
@ -16,4 +16,7 @@ dependencies {
|
|||
/* Project - Core */
|
||||
implementation(projects.core.decompose)
|
||||
implementation(projects.core.ui)
|
||||
|
||||
/* Compose */
|
||||
implementation(deps.compose.runtime)
|
||||
}
|
||||
|
|
@ -13,4 +13,7 @@ dependencies {
|
|||
/* Project - Core */
|
||||
implementation(projects.core.decompose)
|
||||
implementation(projects.core.ui)
|
||||
|
||||
/* Compose */
|
||||
implementation(deps.compose.runtime)
|
||||
}
|
||||
|
|
@ -27,6 +27,7 @@ import kotlinx.coroutines.flow.update
|
|||
|
||||
internal class PreviewManageTokensComponent(
|
||||
private val isLoading: Boolean,
|
||||
private val showTangemIcon: Boolean,
|
||||
params: ManageTokensComponent.Params,
|
||||
) : ManageTokensComponent {
|
||||
|
||||
|
|
@ -65,6 +66,7 @@ internal class PreviewManageTokensComponent(
|
|||
loadMore = { false },
|
||||
saveChanges = {},
|
||||
isSavingInProgress = false,
|
||||
needToAddDerivations = showTangemIcon,
|
||||
),
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ internal data class CustomTokenFormUM(
|
|||
val notifications: PersistentList<NotificationUM> = persistentListOf(),
|
||||
val canAddToken: Boolean = false,
|
||||
val isValidating: Boolean = false,
|
||||
val needToAddDerivation: Boolean = false,
|
||||
val saveToken: () -> Unit,
|
||||
) {
|
||||
|
||||
|
|
|
|||
|
|
@ -14,6 +14,4 @@ internal data class CurrencyNetworkUM(
|
|||
) : SelectableItemUM {
|
||||
|
||||
override val id: String = network.id.value
|
||||
|
||||
data class LongTapConfig(val contractAddress: String, val onLongTap: () -> Unit)
|
||||
}
|
||||
|
|
@ -42,6 +42,7 @@ internal sealed class ManageTokensUM {
|
|||
val saveChanges: () -> Unit,
|
||||
val hasChanges: Boolean,
|
||||
val isSavingInProgress: Boolean,
|
||||
val needToAddDerivations: Boolean,
|
||||
) : ManageTokensUM()
|
||||
|
||||
fun copySealed(
|
||||
|
|
@ -52,6 +53,7 @@ internal sealed class ManageTokensUM {
|
|||
isNextBatchLoading: Boolean = this.isNextBatchLoading,
|
||||
isSavingInProgress: Boolean = this is ManageContent && this.isSavingInProgress,
|
||||
scrollToTop: StateEvent<Unit> = this.scrollToTop,
|
||||
needToAddDerivations: Boolean = this is ManageContent && this.needToAddDerivations,
|
||||
): ManageTokensUM {
|
||||
return when (this) {
|
||||
is ManageContent -> copy(
|
||||
|
|
@ -62,6 +64,7 @@ internal sealed class ManageTokensUM {
|
|||
isNextBatchLoading = isNextBatchLoading,
|
||||
isSavingInProgress = isSavingInProgress,
|
||||
scrollToTop = scrollToTop,
|
||||
needToAddDerivations = needToAddDerivations,
|
||||
)
|
||||
is ReadContent -> copy(
|
||||
search = search,
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import com.tangem.core.ui.extensions.resourceReference
|
|||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.core.ui.message.ContentMessage
|
||||
import com.tangem.domain.card.DerivePublicKeysUseCase
|
||||
import com.tangem.domain.card.HasMissedDerivationsUseCase
|
||||
import com.tangem.domain.managetokens.model.exceptoin.CustomTokenFormValidationException
|
||||
import com.tangem.domain.tokens.AddCryptoCurrenciesUseCase
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
|
|
@ -43,6 +44,7 @@ internal class CustomTokenFormModel @Inject constructor(
|
|||
private val customCurrencyValidator: CustomCurrencyValidator,
|
||||
private val addCryptoCurrenciesUseCase: AddCryptoCurrenciesUseCase,
|
||||
private val derivePublicKeysUseCase: DerivePublicKeysUseCase,
|
||||
private val hasMissedDerivationsUseCase: HasMissedDerivationsUseCase,
|
||||
private val messageSender: UiMessageSender,
|
||||
private val customTokenFormManager: CustomCurrencyFormBuilder,
|
||||
private val analyticsEventHandler: AnalyticsEventHandler,
|
||||
|
|
@ -159,7 +161,12 @@ internal class CustomTokenFormModel @Inject constructor(
|
|||
fillForm: Boolean,
|
||||
isAlreadyAdded: Boolean,
|
||||
isCustom: Boolean,
|
||||
) {
|
||||
) = modelScope.launch {
|
||||
val needToAddDerivation = hasMissedDerivationsUseCase(
|
||||
userWalletId = params.userWalletId,
|
||||
networksWithDerivationPath = mapOf(currency.network.backendId to getDerivationPath().value),
|
||||
)
|
||||
|
||||
state.update { state ->
|
||||
var updatedState = state
|
||||
.updateWithProgress(
|
||||
|
|
@ -169,6 +176,7 @@ internal class CustomTokenFormModel @Inject constructor(
|
|||
clearNotifications = true,
|
||||
clearFieldErrors = true,
|
||||
disableSecondaryFields = !isCustom,
|
||||
needToAddDerivation = needToAddDerivation,
|
||||
)
|
||||
|
||||
if (fillForm) {
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ import com.tangem.core.ui.event.triggeredEvent
|
|||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.core.ui.message.SnackbarMessage
|
||||
import com.tangem.domain.card.HasMissedDerivationsUseCase
|
||||
import com.tangem.domain.managetokens.SaveManagedTokensUseCase
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.features.managetokens.analytics.CustomTokenAnalyticsEvent
|
||||
|
|
@ -47,6 +48,7 @@ internal class ManageTokensModel @Inject constructor(
|
|||
private val router: Router,
|
||||
private val manageTokensListManager: ManageTokensListManager,
|
||||
private val messageSender: UiMessageSender,
|
||||
private val hasMissedDerivationsUseCase: HasMissedDerivationsUseCase,
|
||||
private val saveManagedTokensUseCase: SaveManagedTokensUseCase,
|
||||
private val analyticsEventHandler: AnalyticsEventHandler,
|
||||
paramsContainer: ParamsContainer,
|
||||
|
|
@ -140,6 +142,7 @@ internal class ManageTokensModel @Inject constructor(
|
|||
hasChanges = false,
|
||||
saveChanges = ::saveChanges,
|
||||
loadMore = ::loadMoreItems,
|
||||
needToAddDerivations = false,
|
||||
isSavingInProgress = false,
|
||||
)
|
||||
}
|
||||
|
|
@ -255,10 +258,22 @@ internal class ManageTokensModel @Inject constructor(
|
|||
}
|
||||
|
||||
private fun updateChangedItems(currenciesToAdd: ChangedCurrencies, currenciesToRemove: ChangedCurrencies) {
|
||||
state.update { state ->
|
||||
state.copySealed(
|
||||
hasChanges = currenciesToAdd.isNotEmpty() || currenciesToRemove.isNotEmpty(),
|
||||
)
|
||||
modelScope.launch {
|
||||
val hasMissedDerivations = params.userWalletId?.let { walletId ->
|
||||
val networks = currenciesToAdd.values
|
||||
.flatten()
|
||||
.toSet()
|
||||
.associate { it.backendId to null }
|
||||
|
||||
hasMissedDerivationsUseCase(walletId, networks)
|
||||
}
|
||||
|
||||
state.update { state ->
|
||||
state.copySealed(
|
||||
hasChanges = currenciesToAdd.isNotEmpty() || currenciesToRemove.isNotEmpty(),
|
||||
needToAddDerivations = hasMissedDerivations ?: false,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -61,9 +61,11 @@ internal class OnboardingManageTokensModel @Inject constructor(
|
|||
.onEach { status -> updatePaginationStatus(status) }
|
||||
.launchIn(modelScope)
|
||||
|
||||
manageTokensListManager.currenciesToAdd
|
||||
.onEach(::handleNewAddedCurrencies)
|
||||
.launchIn(modelScope)
|
||||
combine(
|
||||
manageTokensListManager.currenciesToAdd,
|
||||
manageTokensListManager.currenciesToRemove,
|
||||
::handleChangedCurrencies,
|
||||
).launchIn(modelScope)
|
||||
|
||||
observeSearchQueryChanges()
|
||||
|
||||
|
|
@ -192,8 +194,11 @@ internal class OnboardingManageTokensModel @Inject constructor(
|
|||
state.update { state -> state.copy(scrollToTop = consumedEvent()) }
|
||||
}
|
||||
|
||||
private suspend fun handleNewAddedCurrencies(currenciesToAdd: ChangedCurrencies) {
|
||||
if (currenciesToAdd.isEmpty()) {
|
||||
private suspend fun handleChangedCurrencies(
|
||||
currenciesToAdd: ChangedCurrencies,
|
||||
currenciesToRemove: ChangedCurrencies,
|
||||
) {
|
||||
if (currenciesToAdd.isEmpty() && currenciesToRemove.isEmpty()) {
|
||||
state.update { state ->
|
||||
state.copy(
|
||||
actionButtonConfig = OnboardingManageTokensUM.ActionButtonConfig.Later(onClick = ::onLaterClick),
|
||||
|
|
|
|||
|
|
@ -22,9 +22,11 @@ import androidx.compose.ui.tooling.preview.PreviewParameter
|
|||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.util.fastForEach
|
||||
import com.tangem.core.ui.components.PrimaryButton
|
||||
import com.tangem.core.ui.components.block.information.InformationBlock
|
||||
import com.tangem.core.ui.components.bottomFade
|
||||
import com.tangem.core.ui.components.buttons.common.TangemButton
|
||||
import com.tangem.core.ui.components.buttons.common.TangemButtonIconPosition
|
||||
import com.tangem.core.ui.components.buttons.common.TangemButtonsDefaults
|
||||
import com.tangem.core.ui.components.fields.SimpleTextField
|
||||
import com.tangem.core.ui.components.isOpened
|
||||
import com.tangem.core.ui.components.keyboardAsState
|
||||
|
|
@ -82,14 +84,22 @@ internal fun CustomTokenFormContent(model: CustomTokenFormUM, modifier: Modifier
|
|||
}
|
||||
}
|
||||
|
||||
PrimaryButton(
|
||||
TangemButton(
|
||||
modifier = Modifier
|
||||
.align(Alignment.BottomCenter)
|
||||
.padding(bottom = TangemTheme.dimens.spacing16 + bottomBarHeight)
|
||||
.fillMaxWidth(),
|
||||
text = stringResource(id = R.string.custom_token_add_token),
|
||||
colors = TangemButtonsDefaults.primaryButtonColors,
|
||||
enabled = model.canAddToken,
|
||||
showProgress = model.isValidating,
|
||||
animateContentChange = true,
|
||||
icon = if (model.needToAddDerivation) {
|
||||
TangemButtonIconPosition.End(R.drawable.ic_tangem_24)
|
||||
} else {
|
||||
TangemButtonIconPosition.None
|
||||
},
|
||||
textStyle = TangemTheme.typography.subtitle1,
|
||||
onClick = model.saveToken,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -285,6 +285,18 @@ private class CustomTokenNetworkSelectorComponentPreviewProvider :
|
|||
onDerivationPathSelected = {},
|
||||
),
|
||||
),
|
||||
PreviewCustomTokenSelectorComponent(
|
||||
params = CustomTokenSelectorComponent.Params.NetworkSelector(
|
||||
userWalletId = UserWalletId(stringValue = "321"),
|
||||
selectedNetwork = SelectedNetwork(
|
||||
id = Network.ID(value = "0"),
|
||||
name = "Ethereum",
|
||||
derivationPath = Network.DerivationPath.Card("m/44'/0'/0'/0/0"),
|
||||
canHandleTokens = true,
|
||||
),
|
||||
onNetworkSelected = {},
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
// endregion Preview
|
||||
|
|
@ -33,11 +33,17 @@ import androidx.compose.ui.tooling.preview.PreviewParameter
|
|||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.util.fastForEachIndexed
|
||||
import com.tangem.core.ui.components.*
|
||||
import com.tangem.core.ui.components.BottomFade
|
||||
import com.tangem.core.ui.components.RectangleShimmer
|
||||
import com.tangem.core.ui.components.TangemSwitch
|
||||
import com.tangem.core.ui.components.TextShimmer
|
||||
import com.tangem.core.ui.components.appbar.TangemTopAppBar
|
||||
import com.tangem.core.ui.components.appbar.models.TopAppBarButtonUM
|
||||
import com.tangem.core.ui.components.buttons.SecondarySmallButton
|
||||
import com.tangem.core.ui.components.buttons.SmallButtonConfig
|
||||
import com.tangem.core.ui.components.buttons.common.TangemButton
|
||||
import com.tangem.core.ui.components.buttons.common.TangemButtonIconPosition
|
||||
import com.tangem.core.ui.components.buttons.common.TangemButtonsDefaults
|
||||
import com.tangem.core.ui.components.currency.icon.CurrencyIcon
|
||||
import com.tangem.core.ui.components.currency.icon.CurrencyIconState
|
||||
import com.tangem.core.ui.components.fields.SearchBar
|
||||
|
|
@ -122,6 +128,7 @@ internal fun ManageTokensScreen(state: ManageTokensUM, modifier: Modifier = Modi
|
|||
.fillMaxWidth(),
|
||||
isVisible = state.hasChanges,
|
||||
showProgress = state.isSavingInProgress,
|
||||
showIcon = state.needToAddDerivations,
|
||||
onClick = state.saveChanges,
|
||||
)
|
||||
}
|
||||
|
|
@ -158,6 +165,7 @@ private fun ManageTokensTopBar(topBar: ManageTokensTopBarUM?, search: SearchBarU
|
|||
private fun SaveChangesButton(
|
||||
isVisible: Boolean,
|
||||
showProgress: Boolean,
|
||||
showIcon: Boolean,
|
||||
onClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
|
|
@ -168,10 +176,18 @@ private fun SaveChangesButton(
|
|||
exit = fadeOut(),
|
||||
label = "save_button_visibility",
|
||||
) {
|
||||
PrimaryButtonIconEnd(
|
||||
TangemButton(
|
||||
text = stringResource(id = R.string.common_save),
|
||||
iconResId = R.drawable.ic_tangem_24,
|
||||
icon = if (showIcon) {
|
||||
TangemButtonIconPosition.End(R.drawable.ic_tangem_24)
|
||||
} else {
|
||||
TangemButtonIconPosition.None
|
||||
},
|
||||
showProgress = showProgress,
|
||||
colors = TangemButtonsDefaults.primaryButtonColors,
|
||||
textStyle = TangemTheme.typography.subtitle1,
|
||||
enabled = true,
|
||||
animateContentChange = true,
|
||||
onClick = onClick,
|
||||
)
|
||||
}
|
||||
|
|
@ -472,6 +488,7 @@ private class PreviewManageTokensComponentProvider : PreviewParameterProvider<Ma
|
|||
get() = sequenceOf(
|
||||
PreviewManageTokensComponent(
|
||||
isLoading = true,
|
||||
showTangemIcon = true,
|
||||
params = ManageTokensComponent.Params(
|
||||
source = ManageTokensSource.ONBOARDING,
|
||||
userWalletId = UserWalletId("wallet_id"),
|
||||
|
|
@ -479,10 +496,12 @@ private class PreviewManageTokensComponentProvider : PreviewParameterProvider<Ma
|
|||
),
|
||||
PreviewManageTokensComponent(
|
||||
isLoading = false,
|
||||
showTangemIcon = true,
|
||||
params = ManageTokensComponent.Params(source = ManageTokensSource.ONBOARDING, userWalletId = null),
|
||||
),
|
||||
PreviewManageTokensComponent(
|
||||
isLoading = false,
|
||||
showTangemIcon = false,
|
||||
params = ManageTokensComponent.Params(
|
||||
source = ManageTokensSource.ONBOARDING,
|
||||
userWalletId = UserWalletId("wallet_id"),
|
||||
|
|
|
|||
|
|
@ -68,6 +68,7 @@ internal class ManageTokensListManager @Inject constructor(
|
|||
dispatchers = dispatchers,
|
||||
actions = this,
|
||||
scopeProvider = Provider { scope },
|
||||
sourceProvider = Provider { source },
|
||||
clipboardManager = clipboardManager,
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import com.tangem.core.ui.message.SnackbarMessage
|
|||
import com.tangem.domain.managetokens.model.CurrencyUnsupportedState
|
||||
import com.tangem.domain.managetokens.model.ManagedCryptoCurrency
|
||||
import com.tangem.domain.tokens.model.Network
|
||||
import com.tangem.features.managetokens.component.ManageTokensSource
|
||||
import com.tangem.features.managetokens.entity.item.CurrencyItemUM
|
||||
import com.tangem.features.managetokens.impl.R
|
||||
import com.tangem.features.managetokens.ui.dialog.CurrencyUnsupportedDialog
|
||||
|
|
@ -29,11 +30,13 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi
|
|||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
internal class ManageTokensUiManager(
|
||||
private val state: MutableStateFlow<ManageTokensListState>,
|
||||
private val messageSender: UiMessageSender,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
private val scopeProvider: Provider<CoroutineScope>,
|
||||
private val sourceProvider: Provider<ManageTokensSource>,
|
||||
private val actions: ManageTokensUiActions,
|
||||
private val clipboardManager: ClipboardManager,
|
||||
) {
|
||||
|
|
@ -41,6 +44,9 @@ internal class ManageTokensUiManager(
|
|||
private val scope: CoroutineScope
|
||||
get() = scopeProvider()
|
||||
|
||||
private val source: ManageTokensSource
|
||||
get() = sourceProvider()
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
val items: Flow<ImmutableList<CurrencyItemUM>> = state
|
||||
.mapLatest { state ->
|
||||
|
|
@ -225,26 +231,39 @@ internal class ManageTokensUiManager(
|
|||
} else {
|
||||
actions.checkHasLinkedTokens(userWalletId, network)
|
||||
}
|
||||
val canHideWithoutConfirming = source == ManageTokensSource.ONBOARDING
|
||||
|
||||
val message = ContentMessage { onDismiss ->
|
||||
if (hasLinkedTokens) {
|
||||
HasLinkedTokensWarning(
|
||||
currency = currency,
|
||||
network = network,
|
||||
onDismiss = onDismiss,
|
||||
)
|
||||
} else {
|
||||
HideTokenWarning(
|
||||
currency = currency,
|
||||
onConfirm = {
|
||||
onConfirm()
|
||||
onDismiss()
|
||||
},
|
||||
onDismiss = onDismiss,
|
||||
)
|
||||
}
|
||||
if (hasLinkedTokens) {
|
||||
showLinkedTokensWarning(currency, network)
|
||||
} else if (canHideWithoutConfirming) {
|
||||
onConfirm()
|
||||
} else {
|
||||
showHideTokenWarning(currency, onConfirm)
|
||||
}
|
||||
}
|
||||
|
||||
private fun showLinkedTokensWarning(currency: ManagedCryptoCurrency, network: Network) {
|
||||
val message = ContentMessage { onDismiss ->
|
||||
HasLinkedTokensWarning(
|
||||
currency = currency,
|
||||
network = network,
|
||||
onDismiss = onDismiss,
|
||||
)
|
||||
}
|
||||
messageSender.send(message)
|
||||
}
|
||||
|
||||
private fun showHideTokenWarning(currency: ManagedCryptoCurrency, onConfirm: () -> Unit) {
|
||||
val message = ContentMessage { onDismiss ->
|
||||
HideTokenWarning(
|
||||
currency = currency,
|
||||
onConfirm = {
|
||||
onConfirm()
|
||||
onDismiss()
|
||||
},
|
||||
onDismiss = onDismiss,
|
||||
)
|
||||
}
|
||||
messageSender.send(message)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ internal fun CustomTokenFormUM.updateWithProgress(
|
|||
showProgress: Boolean,
|
||||
isWasFilled: Boolean = this.tokenForm?.wasFilled ?: false,
|
||||
canAddToken: Boolean = this.canAddToken,
|
||||
needToAddDerivation: Boolean = false,
|
||||
clearNotifications: Boolean = false,
|
||||
clearFieldErrors: Boolean = false,
|
||||
disableSecondaryFields: Boolean = false,
|
||||
|
|
@ -34,6 +35,7 @@ internal fun CustomTokenFormUM.updateWithProgress(
|
|||
return copy(
|
||||
isValidating = showProgress,
|
||||
canAddToken = canAddToken,
|
||||
needToAddDerivation = needToAddDerivation,
|
||||
notifications = if (clearNotifications) persistentListOf() else notifications,
|
||||
).updateTokenForm {
|
||||
val updatedFields = fields.mapValues { (key, field) ->
|
||||
|
|
|
|||
|
|
@ -1,5 +0,0 @@
|
|||
package com.tangem.features.markets
|
||||
|
||||
interface MarketsFeatureToggles {
|
||||
val isFeatureEnabled: Boolean
|
||||
}
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
package com.tangem.features.markets
|
||||
|
||||
import com.tangem.core.featuretoggle.manager.FeatureTogglesManager
|
||||
|
||||
internal class DefaultMarketsFeatureToggles(
|
||||
private val featureTogglesManager: FeatureTogglesManager,
|
||||
) : MarketsFeatureToggles {
|
||||
|
||||
override val isFeatureEnabled: Boolean
|
||||
get() = featureTogglesManager.isFeatureEnabled("MARKETS_ENABLED")
|
||||
}
|
||||
|
|
@ -17,6 +17,8 @@ import com.tangem.core.ui.components.marketprice.PriceChangeType
|
|||
import com.tangem.core.ui.event.consumedEvent
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.format.bigdecimal.format
|
||||
import com.tangem.core.ui.format.bigdecimal.percent
|
||||
import com.tangem.core.ui.utils.BigDecimalFormatter
|
||||
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
|
|
@ -172,12 +174,7 @@ internal class MarketsTokenDetailsModel @Inject constructor(
|
|||
fiatCurrencySymbol = currentAppCurrency.value.symbol,
|
||||
),
|
||||
dateTimeText = resourceReference(R.string.common_today),
|
||||
priceChangePercentText = params.token.tokenQuotes.h24Percent?.let {
|
||||
BigDecimalFormatter.formatPercent(
|
||||
percent = it,
|
||||
useAbsoluteValue = true,
|
||||
)
|
||||
},
|
||||
priceChangePercentText = params.token.tokenQuotes.h24Percent?.format { percent() },
|
||||
priceChangeType = params.token.tokenQuotes.h24Percent.percentChangeType(),
|
||||
iconUrl = params.token.imageUrl,
|
||||
chartState = MarketsTokenDetailsUM.ChartState(
|
||||
|
|
@ -472,12 +469,7 @@ internal class MarketsTokenDetailsModel @Inject constructor(
|
|||
)
|
||||
} ?: currentQuotes.value.getPercentByInterval(currentState.selectedInterval)
|
||||
|
||||
val percentText = percent?.let {
|
||||
BigDecimalFormatter.formatPercent(
|
||||
percent = it,
|
||||
useAbsoluteValue = true,
|
||||
)
|
||||
} ?: ""
|
||||
val percentText = percent?.format { percent() } ?: ""
|
||||
|
||||
state.update { stateToUpdate ->
|
||||
stateToUpdate.copy(
|
||||
|
|
|
|||
|
|
@ -3,7 +3,10 @@ package com.tangem.features.markets.details.impl.model.converters
|
|||
import androidx.compose.runtime.Stable
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.wrappedList
|
||||
import com.tangem.core.ui.utils.BigDecimalFormatter
|
||||
import com.tangem.core.ui.format.bigdecimal.compact
|
||||
import com.tangem.core.ui.format.bigdecimal.fiat
|
||||
import com.tangem.core.ui.format.bigdecimal.format
|
||||
import com.tangem.core.ui.format.bigdecimal.rawCompact
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.markets.PriceChangeInterval
|
||||
import com.tangem.domain.markets.TokenMarketInfo
|
||||
|
|
@ -142,14 +145,17 @@ internal class InsightsConverter(
|
|||
|
||||
private fun BigDecimal.convertChange(isFiatValue: Boolean = false): String {
|
||||
val value = if (isFiatValue) {
|
||||
val currency = appCurrency()
|
||||
BigDecimalFormatter.formatCompactFiatAmount(
|
||||
amount = this.abs(),
|
||||
fiatCurrencyCode = currency.code,
|
||||
fiatCurrencySymbol = currency.symbol,
|
||||
)
|
||||
this.abs().format {
|
||||
val currency = appCurrency()
|
||||
fiat(
|
||||
fiatCurrencyCode = currency.code,
|
||||
fiatCurrencySymbol = currency.symbol,
|
||||
).compact()
|
||||
}
|
||||
} else {
|
||||
BigDecimalFormatter.formatCompactAmount(amount = this.abs())
|
||||
this.abs().format {
|
||||
rawCompact()
|
||||
}
|
||||
}
|
||||
|
||||
return when {
|
||||
|
|
|
|||
|
|
@ -2,7 +2,10 @@ package com.tangem.features.markets.details.impl.model.converters
|
|||
|
||||
import androidx.compose.runtime.Stable
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.utils.BigDecimalFormatter
|
||||
import com.tangem.core.ui.format.bigdecimal.compact
|
||||
import com.tangem.core.ui.format.bigdecimal.crypto
|
||||
import com.tangem.core.ui.format.bigdecimal.fiat
|
||||
import com.tangem.core.ui.format.bigdecimal.format
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.markets.TokenMarketInfo
|
||||
import com.tangem.features.markets.details.impl.ui.state.InfoBottomSheetContent
|
||||
|
|
@ -129,18 +132,21 @@ internal class MetricsConverter(
|
|||
if (this == null) return StringsSigns.DASH_SIGN
|
||||
|
||||
return if (crypto) {
|
||||
BigDecimalFormatter.formatCompactCryptoAmount(
|
||||
amount = this,
|
||||
cryptoCurrencySymbol = tokenSymbol,
|
||||
)
|
||||
format {
|
||||
crypto(
|
||||
symbol = tokenSymbol,
|
||||
decimals = 2,
|
||||
).compact()
|
||||
}
|
||||
} else {
|
||||
val currency = appCurrency()
|
||||
|
||||
BigDecimalFormatter.formatCompactFiatAmount(
|
||||
amount = this,
|
||||
fiatCurrencyCode = currency.code,
|
||||
fiatCurrencySymbol = currency.symbol,
|
||||
)
|
||||
format {
|
||||
fiat(
|
||||
fiatCurrencyCode = currency.code,
|
||||
fiatCurrencySymbol = currency.symbol,
|
||||
).compact()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2,6 +2,8 @@ package com.tangem.features.markets.details.impl.model.formatter
|
|||
|
||||
import com.tangem.common.ui.charts.state.MarketChartLook
|
||||
import com.tangem.core.ui.components.marketprice.PriceChangeType
|
||||
import com.tangem.core.ui.format.bigdecimal.format
|
||||
import com.tangem.core.ui.format.bigdecimal.percent
|
||||
import com.tangem.core.ui.utils.BigDecimalFormatter
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.markets.PriceChangeInterval
|
||||
|
|
@ -28,12 +30,7 @@ internal fun TokenQuotes.getFormattedPercentByInterval(interval: PriceChangeInte
|
|||
PriceChangeInterval.ALL_TIME -> allTimeChangePercent
|
||||
}
|
||||
|
||||
return percent?.let {
|
||||
BigDecimalFormatter.formatPercent(
|
||||
percent = it,
|
||||
useAbsoluteValue = true,
|
||||
)
|
||||
} ?: ""
|
||||
return percent?.format { percent() } ?: ""
|
||||
}
|
||||
|
||||
internal fun TokenQuotes.getPercentByInterval(interval: PriceChangeInterval): BigDecimal? {
|
||||
|
|
|
|||
|
|
@ -8,12 +8,14 @@ import androidx.compose.foundation.shape.RoundedCornerShape
|
|||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.key
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.util.fastForEachIndexed
|
||||
import com.tangem.core.ui.components.PrimaryButton
|
||||
import com.tangem.core.ui.components.RectangleShimmer
|
||||
import com.tangem.core.ui.components.SmallButtonShimmer
|
||||
import com.tangem.core.ui.components.TextShimmer
|
||||
import com.tangem.core.ui.components.block.information.InformationBlock
|
||||
|
|
@ -76,10 +78,26 @@ private fun Title() {
|
|||
private fun AddButton(state: AddButtonState, onClick: () -> Unit) {
|
||||
when (state) {
|
||||
AddButtonState.Loading -> {
|
||||
SmallButtonShimmer(
|
||||
modifier = Modifier.size(width = TangemTheme.dimens.size63, height = TangemTheme.dimens.size18),
|
||||
shape = RoundedCornerShape(TangemTheme.dimens.radius3),
|
||||
)
|
||||
Box {
|
||||
SmallButtonShimmer(
|
||||
modifier = Modifier.width(width = TangemTheme.dimens.size63),
|
||||
shape = RoundedCornerShape(TangemTheme.dimens.radius3),
|
||||
withIcon = true,
|
||||
)
|
||||
|
||||
Box(
|
||||
Modifier
|
||||
.matchParentSize()
|
||||
.background(TangemTheme.colors.background.action),
|
||||
)
|
||||
|
||||
RectangleShimmer(
|
||||
modifier = Modifier
|
||||
.align(Alignment.Center)
|
||||
.size(width = TangemTheme.dimens.size63, height = TangemTheme.dimens.size18),
|
||||
radius = TangemTheme.dimens.radius3,
|
||||
)
|
||||
}
|
||||
}
|
||||
AddButtonState.Available,
|
||||
AddButtonState.Unavailable,
|
||||
|
|
|
|||
|
|
@ -11,6 +11,8 @@ import com.tangem.core.decompose.model.Model
|
|||
import com.tangem.core.decompose.model.ParamsContainer
|
||||
import com.tangem.core.decompose.navigation.Router
|
||||
import com.tangem.core.ui.components.marketprice.PriceChangeType
|
||||
import com.tangem.core.ui.format.bigdecimal.format
|
||||
import com.tangem.core.ui.format.bigdecimal.percent
|
||||
import com.tangem.core.ui.utils.BigDecimalFormatter
|
||||
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
|
|
@ -91,10 +93,7 @@ internal class TokenMarketBlockModel @Inject constructor(
|
|||
// TODO get currency from quotes use case [REDACTED_TASK_KEY]
|
||||
fiatCurrencySymbol = currentAppCurrency.value.symbol,
|
||||
),
|
||||
h24Percent = BigDecimalFormatter.formatPercent(
|
||||
percent = res.priceChange,
|
||||
useAbsoluteValue = true,
|
||||
),
|
||||
h24Percent = res.priceChange.format { percent() },
|
||||
priceChangeType = PriceChangeType.fromBigDecimal(res.priceChange),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,10 @@ import com.tangem.common.ui.charts.state.MarketChartRawData
|
|||
import com.tangem.common.ui.charts.state.converter.PriceAndTimePointValuesConverter
|
||||
import com.tangem.common.ui.charts.state.sorted
|
||||
import com.tangem.core.ui.components.marketprice.PriceChangeType
|
||||
import com.tangem.core.ui.format.bigdecimal.compact
|
||||
import com.tangem.core.ui.format.bigdecimal.fiat
|
||||
import com.tangem.core.ui.format.bigdecimal.format
|
||||
import com.tangem.core.ui.format.bigdecimal.percent
|
||||
import com.tangem.core.ui.utils.BigDecimalFormatter
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.markets.TokenMarket
|
||||
|
|
@ -71,12 +75,14 @@ internal class MarketsTokenItemConverter(
|
|||
private fun TokenMarket.getMarketCap(): String? {
|
||||
val value = marketCap?.takeIf { marketCap != BigDecimal.ZERO } ?: return null
|
||||
|
||||
return BigDecimalFormatter.formatCompactFiatAmount(
|
||||
amount = value,
|
||||
fiatCurrencyCode = appCurrency.code,
|
||||
fiatCurrencySymbol = appCurrency.symbol,
|
||||
threeDigitsMethod = true,
|
||||
)
|
||||
return value.format {
|
||||
fiat(
|
||||
fiatCurrencyCode = appCurrency.code,
|
||||
fiatCurrencySymbol = appCurrency.symbol,
|
||||
).compact(
|
||||
threeDigitsMethod = true,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun TokenMarket.getCurrentPrice(prev: TokenMarket? = null): MarketsListItemUM.Price {
|
||||
|
|
@ -144,9 +150,6 @@ internal class MarketsTokenItemConverter(
|
|||
TrendInterval.M1 -> tokenQuotesShort.monthChangePercent
|
||||
}
|
||||
|
||||
return BigDecimalFormatter.formatPercent(
|
||||
percent = percent,
|
||||
useAbsoluteValue = true,
|
||||
)
|
||||
return percent.format { percent() }
|
||||
}
|
||||
}
|
||||
1
features/onboarding-v2/api/.gitignore
vendored
Normal file
1
features/onboarding-v2/api/.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
/build
|
||||
22
features/onboarding-v2/api/build.gradle.kts
Normal file
22
features/onboarding-v2/api/build.gradle.kts
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
plugins {
|
||||
alias(deps.plugins.android.library)
|
||||
alias(deps.plugins.kotlin.android)
|
||||
id("configuration")
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "com.tangem.features.onboarding.v2.api"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
||||
/* Project - Domain */
|
||||
implementation(projects.domain.wallets.models)
|
||||
|
||||
/* Project - Core */
|
||||
implementation(projects.core.decompose)
|
||||
implementation(projects.core.ui)
|
||||
|
||||
/* Compose */
|
||||
implementation(deps.compose.runtime)
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
package com.tangem.features.onboarding.v2
|
||||
|
||||
interface OnboardingV2FeatureToggles {
|
||||
val isRewrittenOnboardingCodeInUse: Boolean
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
package com.tangem.features.onboarding.v2.entry
|
||||
|
||||
import com.tangem.core.decompose.factory.ComponentFactory
|
||||
import com.tangem.core.ui.decompose.ComposableContentComponent
|
||||
|
||||
interface OnboardingEntryComponent : ComposableContentComponent {
|
||||
|
||||
data class Params(
|
||||
val todo: String,
|
||||
)
|
||||
|
||||
interface Factory : ComponentFactory<Params, OnboardingEntryComponent>
|
||||
}
|
||||
1
features/onboarding-v2/impl/.gitignore
vendored
Normal file
1
features/onboarding-v2/impl/.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
/build
|
||||
58
features/onboarding-v2/impl/build.gradle.kts
Normal file
58
features/onboarding-v2/impl/build.gradle.kts
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
plugins {
|
||||
alias(deps.plugins.android.library)
|
||||
alias(deps.plugins.kotlin.android)
|
||||
alias(deps.plugins.kotlin.kapt)
|
||||
alias(deps.plugins.kotlin.serialization)
|
||||
alias(deps.plugins.hilt.android)
|
||||
id("configuration")
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "com.tangem.features.onboarding.v2.impl"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
/** Api */
|
||||
implementation(projects.features.onboardingV2.api)
|
||||
|
||||
/** Core modules */
|
||||
implementation(projects.core.featuretoggles)
|
||||
implementation(projects.core.analytics)
|
||||
implementation(projects.core.analytics.models)
|
||||
implementation(projects.core.utils)
|
||||
implementation(projects.core.ui)
|
||||
implementation(projects.core.res)
|
||||
implementation(projects.core.decompose)
|
||||
|
||||
/** Domain */
|
||||
implementation(projects.domain.models)
|
||||
implementation(projects.domain.feedback)
|
||||
implementation(projects.domain.core)
|
||||
|
||||
/** Tangem libraries */
|
||||
implementation(deps.tangem.card.core)
|
||||
implementation(deps.tangem.card.android) {
|
||||
exclude(module = "joda-time")
|
||||
}
|
||||
|
||||
/** AndroidX libraries */
|
||||
implementation(deps.androidx.core.ktx)
|
||||
implementation(deps.lifecycle.runtime.ktx)
|
||||
|
||||
/** Compose libraries */
|
||||
implementation(deps.compose.material3)
|
||||
implementation(deps.compose.animation)
|
||||
implementation(deps.compose.foundation)
|
||||
implementation(deps.compose.ui)
|
||||
implementation(deps.compose.ui.tooling)
|
||||
implementation(deps.compose.coil)
|
||||
|
||||
/** Other libraries */
|
||||
implementation(deps.kotlin.immutable.collections)
|
||||
implementation(deps.kotlin.serialization)
|
||||
implementation(deps.timber)
|
||||
|
||||
/** DI */
|
||||
implementation(deps.hilt.android)
|
||||
kapt(deps.hilt.kapt)
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
package com.tangem.features.onboarding.v2
|
||||
|
||||
import com.tangem.core.featuretoggle.manager.FeatureTogglesManager
|
||||
import javax.inject.Inject
|
||||
|
||||
internal class DefaultOnboardingV2FeatureToggles @Inject constructor(
|
||||
private val featureTogglesManager: FeatureTogglesManager,
|
||||
) : OnboardingV2FeatureToggles {
|
||||
override val isRewrittenOnboardingCodeInUse: Boolean
|
||||
get() = featureTogglesManager.isFeatureEnabled("ONBOARDING_CODE_REFACTORING_ENABLED")
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
package com.tangem.features.onboarding.v2.di
|
||||
|
||||
import com.tangem.features.onboarding.v2.DefaultOnboardingV2FeatureToggles
|
||||
import com.tangem.features.onboarding.v2.OnboardingV2FeatureToggles
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
internal interface FeatureModule {
|
||||
|
||||
@Singleton
|
||||
@Binds
|
||||
fun provideFeatureToggles(featureToggles: DefaultOnboardingV2FeatureToggles): OnboardingV2FeatureToggles
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
package com.tangem.features.onboarding.v2.stepper.api
|
||||
|
||||
import androidx.annotation.IntRange
|
||||
import com.tangem.core.decompose.factory.ComponentFactory
|
||||
import com.tangem.core.ui.decompose.ComposableContentComponent
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
|
||||
interface OnboardingStepperComponent : ComposableContentComponent {
|
||||
|
||||
data class StepperState(
|
||||
@IntRange(from = 0) val currentStep: Int,
|
||||
@IntRange(from = 0) val steps: Int,
|
||||
val title: TextReference,
|
||||
)
|
||||
|
||||
class Params(
|
||||
val initState: StepperState,
|
||||
val popBack: () -> Unit,
|
||||
val scanResponse: ScanResponse,
|
||||
)
|
||||
|
||||
val state: StateFlow<StepperState>
|
||||
|
||||
fun changeStep(step: Int)
|
||||
fun nextStep()
|
||||
fun changeTitle(title: TextReference)
|
||||
|
||||
interface Factory : ComponentFactory<Params, OnboardingStepperComponent>
|
||||
}
|
||||
|
|
@ -0,0 +1,76 @@
|
|||
package com.tangem.features.onboarding.v2.stepper.impl
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.arkivanov.essenty.instancekeeper.getOrCreateSimple
|
||||
import com.tangem.core.decompose.context.AppComponentContext
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.domain.feedback.GetCardInfoUseCase
|
||||
import com.tangem.domain.feedback.SendFeedbackEmailUseCase
|
||||
import com.tangem.domain.feedback.models.FeedbackEmailType
|
||||
import com.tangem.features.onboarding.v2.stepper.api.OnboardingStepperComponent
|
||||
import com.tangem.features.onboarding.v2.stepper.impl.ui.OnboardingStepper
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
internal class DefaultOnboardingStepperComponent @AssistedInject constructor(
|
||||
@Assisted val context: AppComponentContext,
|
||||
@Assisted val params: OnboardingStepperComponent.Params,
|
||||
private val getCardInfoUseCase: GetCardInfoUseCase,
|
||||
private val sendFeedbackEmailUseCase: SendFeedbackEmailUseCase,
|
||||
) : OnboardingStepperComponent, AppComponentContext by context {
|
||||
|
||||
override val state = instanceKeeper.getOrCreateSimple { MutableStateFlow(params.initState) }
|
||||
|
||||
override fun changeStep(step: Int) {
|
||||
state.update { it.copy(currentStep = step.coerceAtLeast(minimumValue = 0)) }
|
||||
}
|
||||
|
||||
override fun nextStep() {
|
||||
state.update { state ->
|
||||
if (state.currentStep < state.steps - 1) {
|
||||
state.copy(currentStep = state.currentStep + 1)
|
||||
} else {
|
||||
state
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun changeTitle(title: TextReference) {
|
||||
state.update { it.copy(title = title) }
|
||||
}
|
||||
|
||||
private fun openSupport() {
|
||||
componentScope.launch {
|
||||
val cardInfo = getCardInfoUseCase(params.scanResponse).getOrNull() ?: return@launch
|
||||
sendFeedbackEmailUseCase(FeedbackEmailType.DirectUserRequest(cardInfo))
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
override fun Content(modifier: Modifier) {
|
||||
val state by state.collectAsStateWithLifecycle()
|
||||
|
||||
OnboardingStepper(
|
||||
state = state,
|
||||
onBackClick = remember(this) { params.popBack },
|
||||
onSupportButtonClick = remember(this) { ::openSupport },
|
||||
modifier = modifier,
|
||||
)
|
||||
}
|
||||
|
||||
@AssistedFactory
|
||||
interface Factory : OnboardingStepperComponent.Factory {
|
||||
override fun create(
|
||||
context: AppComponentContext,
|
||||
params: OnboardingStepperComponent.Params,
|
||||
): DefaultOnboardingStepperComponent
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
package com.tangem.features.onboarding.v2.stepper.impl.di
|
||||
|
||||
import com.tangem.features.onboarding.v2.stepper.api.OnboardingStepperComponent
|
||||
import com.tangem.features.onboarding.v2.stepper.impl.DefaultOnboardingStepperComponent
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
internal interface ComponentModule {
|
||||
|
||||
@Binds
|
||||
@Singleton
|
||||
fun bindOnboardingStepperComponent(
|
||||
factory: DefaultOnboardingStepperComponent.Factory,
|
||||
): OnboardingStepperComponent.Factory
|
||||
}
|
||||
|
|
@ -0,0 +1,83 @@
|
|||
package com.tangem.features.onboarding.v2.stepper.impl.ui
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.StrokeCap
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.tangem.core.ui.components.appbar.TangemTopAppBar
|
||||
import com.tangem.core.ui.components.appbar.models.TopAppBarButtonUM
|
||||
import com.tangem.core.ui.components.progressbar.LinearProgressIndicator
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.res.TangemAnimations
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.features.onboarding.v2.impl.R
|
||||
import com.tangem.features.onboarding.v2.stepper.api.OnboardingStepperComponent
|
||||
|
||||
@Composable
|
||||
internal fun OnboardingStepper(
|
||||
state: OnboardingStepperComponent.StepperState,
|
||||
onBackClick: () -> Unit,
|
||||
onSupportButtonClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val fraction = state.currentStep.toFloat() / state.steps.coerceAtLeast(1)
|
||||
val animatedIndicatorFraction by TangemAnimations.horizontalIndicatorAsState(targetFraction = fraction)
|
||||
|
||||
Column(
|
||||
modifier = modifier,
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||
) {
|
||||
TangemTopAppBar(
|
||||
startButton = TopAppBarButtonUM.Back(onBackClick),
|
||||
endButton = TopAppBarButtonUM(iconRes = R.drawable.ic_chat_24, onIconClicked = onSupportButtonClick)
|
||||
.takeIf { state.steps != state.currentStep },
|
||||
title = if (state.steps == state.currentStep) {
|
||||
resourceReference(R.string.common_done)
|
||||
} else {
|
||||
state.title
|
||||
},
|
||||
containerColor = TangemTheme.colors.background.primary,
|
||||
modifier = modifier,
|
||||
)
|
||||
|
||||
LinearProgressIndicator(
|
||||
modifier = Modifier
|
||||
.padding(horizontal = 16.dp)
|
||||
.height(4.dp)
|
||||
.fillMaxWidth(),
|
||||
progress = { animatedIndicatorFraction },
|
||||
color = TangemTheme.colors.icon.primary1,
|
||||
backgroundColor = TangemTheme.colors.background.tertiary,
|
||||
strokeCap = StrokeCap.Round,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun Preview() {
|
||||
TangemThemePreview {
|
||||
Box(
|
||||
Modifier
|
||||
.fillMaxSize()
|
||||
.background(TangemTheme.colors.background.primary),
|
||||
) {
|
||||
OnboardingStepper(
|
||||
modifier = Modifier.align(Alignment.TopCenter),
|
||||
state = OnboardingStepperComponent.StepperState(
|
||||
currentStep = 2,
|
||||
steps = 3,
|
||||
title = resourceReference(R.string.common_done),
|
||||
),
|
||||
onBackClick = {},
|
||||
onSupportButtonClick = {},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
1
features/onramp/api/.gitignore
vendored
Normal file
1
features/onramp/api/.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
/build
|
||||
22
features/onramp/api/build.gradle.kts
Normal file
22
features/onramp/api/build.gradle.kts
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
plugins {
|
||||
alias(deps.plugins.android.library)
|
||||
alias(deps.plugins.kotlin.android)
|
||||
id("kotlin-parcelize")
|
||||
id("configuration")
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "com.tangem.features.onramp.api"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
/* Project - Core */
|
||||
implementation(projects.core.decompose)
|
||||
implementation(projects.core.ui)
|
||||
|
||||
/* Project - Domain */
|
||||
implementation(projects.domain.wallets.models)
|
||||
|
||||
/* Compose */
|
||||
implementation(deps.compose.runtime)
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
package com.tangem.features.onramp
|
||||
|
||||
interface OnrampFeatureToggles {
|
||||
|
||||
val isFeatureEnabled: Boolean
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
package com.tangem.features.onramp.component
|
||||
|
||||
import com.tangem.core.decompose.factory.ComponentFactory
|
||||
import com.tangem.core.ui.decompose.ComposableContentComponent
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
|
||||
/**
|
||||
* Buy crypto component
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
interface BuyCryptoComponent : ComposableContentComponent {
|
||||
|
||||
interface Factory : ComponentFactory<Params, BuyCryptoComponent>
|
||||
|
||||
/**
|
||||
* Params
|
||||
*
|
||||
* @property userWalletId user wallet id
|
||||
*/
|
||||
data class Params(val userWalletId: UserWalletId)
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
package com.tangem.features.onramp.component
|
||||
|
||||
import com.tangem.core.decompose.factory.ComponentFactory
|
||||
import com.tangem.core.ui.decompose.ComposableContentComponent
|
||||
|
||||
interface OnrampComponent : ComposableContentComponent {
|
||||
|
||||
class Params
|
||||
|
||||
interface Factory : ComponentFactory<Params, OnrampComponent>
|
||||
}
|
||||
1
features/onramp/impl/.gitignore
vendored
Normal file
1
features/onramp/impl/.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
/build
|
||||
61
features/onramp/impl/build.gradle.kts
Normal file
61
features/onramp/impl/build.gradle.kts
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
plugins {
|
||||
alias(deps.plugins.android.library)
|
||||
alias(deps.plugins.kotlin.android)
|
||||
alias(deps.plugins.kotlin.serialization)
|
||||
alias(deps.plugins.kotlin.kapt)
|
||||
alias(deps.plugins.hilt.android)
|
||||
id("configuration")
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "com.tangem.features.onramp.impl"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
/** Project - API */
|
||||
implementation(projects.features.onramp.api)
|
||||
|
||||
/** Project - Core */
|
||||
implementation(projects.core.decompose)
|
||||
implementation(projects.core.ui)
|
||||
implementation(projects.core.featuretoggles)
|
||||
|
||||
/** Project - Common */
|
||||
implementation(projects.common.ui)
|
||||
|
||||
/** Project - Domain */
|
||||
implementation(projects.domain.appCurrency)
|
||||
implementation(projects.domain.appCurrency.models)
|
||||
implementation(projects.domain.balanceHiding)
|
||||
implementation(projects.domain.balanceHiding.models)
|
||||
implementation(projects.domain.legacy)
|
||||
implementation(projects.domain.tokens)
|
||||
implementation(projects.domain.tokens.models)
|
||||
implementation(projects.domain.wallets)
|
||||
implementation(projects.domain.wallets.models)
|
||||
|
||||
/** DI */
|
||||
implementation(deps.hilt.android)
|
||||
implementation(project(":common:ui"))
|
||||
kapt(deps.hilt.kapt)
|
||||
|
||||
/** AndroidX */
|
||||
implementation(deps.androidx.activity.compose)
|
||||
implementation(deps.lifecycle.compose)
|
||||
|
||||
/** Compose */
|
||||
implementation(deps.compose.ui)
|
||||
implementation(deps.compose.ui.tooling)
|
||||
implementation(deps.compose.accompanist.systemUiController)
|
||||
implementation(deps.compose.foundation)
|
||||
implementation(deps.compose.material3)
|
||||
implementation(deps.compose.shimmer)
|
||||
implementation(deps.compose.coil)
|
||||
implementation(deps.compose.material)
|
||||
|
||||
/** Other */
|
||||
implementation(deps.decompose.ext.compose)
|
||||
implementation(deps.kotlin.immutable.collections)
|
||||
implementation(deps.reKotlin)
|
||||
implementation(deps.timber)
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
package com.tangem.features.onramp
|
||||
|
||||
import com.tangem.core.featuretoggle.manager.FeatureTogglesManager
|
||||
|
||||
internal class DefaultOnrampFeatureToggles(
|
||||
private val featureTogglesManager: FeatureTogglesManager,
|
||||
) : OnrampFeatureToggles {
|
||||
|
||||
override val isFeatureEnabled: Boolean
|
||||
get() = featureTogglesManager.isFeatureEnabled("ONRAMP_ENABLED")
|
||||
}
|
||||
|
|
@ -0,0 +1,65 @@
|
|||
package com.tangem.features.onramp.buy
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.Stable
|
||||
import androidx.compose.ui.Modifier
|
||||
import arrow.core.getOrElse
|
||||
import com.tangem.core.decompose.context.AppComponentContext
|
||||
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.redux.ReduxStateHolder
|
||||
import com.tangem.domain.tokens.legacy.TradeCryptoAction
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
import com.tangem.domain.wallets.usecase.GetWalletsUseCase
|
||||
import com.tangem.features.onramp.component.BuyCryptoComponent
|
||||
import com.tangem.features.onramp.impl.R
|
||||
import com.tangem.features.onramp.selecttoken.OnrampSelectTokenComponent
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@Stable
|
||||
internal class DefaultBuyCryptoComponent @AssistedInject constructor(
|
||||
onrampSelectTokenComponentFactory: OnrampSelectTokenComponent.Factory,
|
||||
@Assisted private val appComponentContext: AppComponentContext,
|
||||
@Assisted private val params: BuyCryptoComponent.Params,
|
||||
private val reduxStateHolder: ReduxStateHolder,
|
||||
private val getWalletsUseCase: GetWalletsUseCase,
|
||||
private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
|
||||
) : BuyCryptoComponent {
|
||||
|
||||
private val selectTokenComponent: OnrampSelectTokenComponent = onrampSelectTokenComponentFactory.create(
|
||||
context = appComponentContext,
|
||||
params = OnrampSelectTokenComponent.Params(
|
||||
hasSearchBar = true,
|
||||
userWalletId = params.userWalletId,
|
||||
titleResId = R.string.common_buy,
|
||||
onTokenClick = ::onTokenClick,
|
||||
),
|
||||
)
|
||||
|
||||
@Composable
|
||||
override fun Content(modifier: Modifier) {
|
||||
selectTokenComponent.Content(modifier = modifier)
|
||||
}
|
||||
|
||||
private fun onTokenClick(status: CryptoCurrencyStatus) {
|
||||
appComponentContext.componentScope.launch {
|
||||
reduxStateHolder.dispatch(
|
||||
TradeCryptoAction.Buy(
|
||||
userWallet = getWalletsUseCase.invokeSync().first { it.walletId == params.userWalletId },
|
||||
cryptoCurrencyStatus = status,
|
||||
appCurrencyCode = getSelectedAppCurrencyUseCase.invokeSync()
|
||||
.getOrElse { AppCurrency.Default }.code,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@AssistedFactory
|
||||
interface Factory : BuyCryptoComponent.Factory {
|
||||
|
||||
override fun create(context: AppComponentContext, params: BuyCryptoComponent.Params): DefaultBuyCryptoComponent
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
package com.tangem.features.onramp.buy.di
|
||||
|
||||
import com.tangem.features.onramp.buy.DefaultBuyCryptoComponent
|
||||
import com.tangem.features.onramp.component.BuyCryptoComponent
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
internal interface BuyCryptoComponentModule {
|
||||
|
||||
@Binds
|
||||
@Singleton
|
||||
fun bindBuyCryptoComponentFactory(factory: DefaultBuyCryptoComponent.Factory): BuyCryptoComponent.Factory
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
package com.tangem.features.onramp.component
|
||||
|
||||
import com.tangem.core.decompose.factory.ComponentFactory
|
||||
import com.tangem.core.ui.decompose.ComposableBottomSheetComponent
|
||||
|
||||
internal interface ConfirmResidencyComponent : ComposableBottomSheetComponent {
|
||||
|
||||
data class Params(
|
||||
val countryName: String,
|
||||
val isOnrampSupported: Boolean,
|
||||
val countryFlagUrl: String,
|
||||
val onDismiss: () -> Unit,
|
||||
)
|
||||
|
||||
interface Factory : ComponentFactory<Params, ConfirmResidencyComponent>
|
||||
}
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
package com.tangem.features.onramp.component.impl
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.tangem.core.decompose.context.AppComponentContext
|
||||
import com.tangem.core.decompose.model.getOrCreateModel
|
||||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
|
||||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent
|
||||
import com.tangem.features.onramp.component.ConfirmResidencyComponent
|
||||
import com.tangem.features.onramp.model.ConfirmResidencyModel
|
||||
import com.tangem.features.onramp.ui.ConfirmResidencyBottomSheet
|
||||
import com.tangem.features.onramp.ui.ConfirmResidencyBottomSheetContent
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
|
||||
internal class DefaultConfirmResidencyComponent @AssistedInject constructor(
|
||||
@Assisted context: AppComponentContext,
|
||||
@Assisted private val params: ConfirmResidencyComponent.Params,
|
||||
) : ConfirmResidencyComponent, AppComponentContext by context {
|
||||
|
||||
private val model: ConfirmResidencyModel = getOrCreateModel(params)
|
||||
|
||||
override fun dismiss() {
|
||||
params.onDismiss()
|
||||
}
|
||||
|
||||
@Composable
|
||||
override fun BottomSheet() {
|
||||
val state by model.state.collectAsStateWithLifecycle()
|
||||
val bottomSheetConfig = remember {
|
||||
TangemBottomSheetConfig(
|
||||
isShow = true,
|
||||
onDismissRequest = ::dismiss,
|
||||
content = TangemBottomSheetConfigContent.Empty,
|
||||
)
|
||||
}
|
||||
ConfirmResidencyBottomSheet(
|
||||
config = bottomSheetConfig,
|
||||
content = { modifier ->
|
||||
ConfirmResidencyBottomSheetContent(model = state, modifier = modifier)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@AssistedFactory
|
||||
interface Factory : ConfirmResidencyComponent.Factory {
|
||||
override fun create(
|
||||
context: AppComponentContext,
|
||||
params: ConfirmResidencyComponent.Params,
|
||||
): DefaultConfirmResidencyComponent
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,64 @@
|
|||
package com.tangem.features.onramp.component.impl
|
||||
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import com.arkivanov.decompose.ComponentContext
|
||||
import com.arkivanov.decompose.extensions.compose.jetpack.subscribeAsState
|
||||
import com.arkivanov.decompose.router.slot.childSlot
|
||||
import com.arkivanov.decompose.router.slot.dismiss
|
||||
import com.tangem.core.decompose.context.AppComponentContext
|
||||
import com.tangem.core.decompose.context.childByContext
|
||||
import com.tangem.core.decompose.model.getOrCreateModel
|
||||
import com.tangem.core.ui.decompose.ComposableBottomSheetComponent
|
||||
import com.tangem.features.onramp.component.OnrampComponent
|
||||
import com.tangem.features.onramp.component.ConfirmResidencyComponent
|
||||
import com.tangem.features.onramp.entity.OnrampBottomSheetConfig
|
||||
import com.tangem.features.onramp.model.OnrampModel
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
|
||||
internal class DefaultOnrampComponent @AssistedInject constructor(
|
||||
@Assisted context: AppComponentContext,
|
||||
@Assisted private val params: OnrampComponent.Params,
|
||||
private val confirmResidencyComponentFactory: ConfirmResidencyComponent.Factory,
|
||||
) : OnrampComponent, AppComponentContext by context {
|
||||
|
||||
private val model: OnrampModel = getOrCreateModel(params)
|
||||
private val confirmResidencyBottomSheetSlot = childSlot(
|
||||
source = model.bottomSheetNavigation,
|
||||
serializer = null,
|
||||
handleBackButton = false,
|
||||
childFactory = ::bottomSheetChild,
|
||||
)
|
||||
|
||||
@Composable
|
||||
override fun Content(modifier: Modifier) {
|
||||
val bottomSheet by confirmResidencyBottomSheetSlot.subscribeAsState()
|
||||
BackHandler(onBack = router::pop)
|
||||
|
||||
bottomSheet.child?.instance?.BottomSheet()
|
||||
}
|
||||
|
||||
private fun bottomSheetChild(
|
||||
config: OnrampBottomSheetConfig,
|
||||
componentContext: ComponentContext,
|
||||
): ComposableBottomSheetComponent = when (config) {
|
||||
OnrampBottomSheetConfig.ConfirmResidency -> confirmResidencyComponentFactory.create(
|
||||
context = childByContext(componentContext),
|
||||
params = ConfirmResidencyComponent.Params(
|
||||
countryName = "United States",
|
||||
isOnrampSupported = true,
|
||||
countryFlagUrl = "https://hatscripts.github.io/circle-flags/flags/us.svg",
|
||||
onDismiss = model.bottomSheetNavigation::dismiss,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@AssistedFactory
|
||||
interface Factory : OnrampComponent.Factory {
|
||||
override fun create(context: AppComponentContext, params: OnrampComponent.Params): DefaultOnrampComponent
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
package com.tangem.features.onramp.component.preview
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
|
||||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.features.onramp.component.ConfirmResidencyComponent
|
||||
import com.tangem.features.onramp.entity.ConfirmResidencyUM
|
||||
import com.tangem.features.onramp.ui.ConfirmResidencyBottomSheet
|
||||
import com.tangem.features.onramp.ui.ConfirmResidencyBottomSheetContent
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
|
||||
internal class PreviewConfirmResidencyComponent(
|
||||
initialState: ConfirmResidencyUM = ConfirmResidencyUM(
|
||||
country = "United States",
|
||||
countryFlagUrl = "https://hatscripts.github.io/circle-flags/flags/us.svg",
|
||||
isCountrySupported = true,
|
||||
primaryButtonConfig = ConfirmResidencyUM.ActionButtonConfig(onClick = {}, text = stringReference("Confirm")),
|
||||
secondaryButtonConfig = ConfirmResidencyUM.ActionButtonConfig(onClick = {}, text = stringReference("Change")),
|
||||
),
|
||||
) : ConfirmResidencyComponent {
|
||||
|
||||
private val previewState: MutableStateFlow<ConfirmResidencyUM> = MutableStateFlow(initialState)
|
||||
|
||||
override fun dismiss() {
|
||||
/* no-op */
|
||||
}
|
||||
|
||||
@Composable
|
||||
override fun BottomSheet() {
|
||||
val state by previewState.collectAsStateWithLifecycle()
|
||||
val bottomSheetConfig = TangemBottomSheetConfig(
|
||||
isShow = true,
|
||||
onDismissRequest = ::dismiss,
|
||||
content = TangemBottomSheetConfigContent.Empty,
|
||||
)
|
||||
ConfirmResidencyBottomSheet(
|
||||
config = bottomSheetConfig,
|
||||
content = { modifier ->
|
||||
ConfirmResidencyBottomSheetContent(model = state, modifier = modifier)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
package com.tangem.features.onramp.di
|
||||
|
||||
import com.tangem.features.onramp.component.ConfirmResidencyComponent
|
||||
import com.tangem.features.onramp.component.OnrampComponent
|
||||
import com.tangem.features.onramp.component.impl.DefaultConfirmResidencyComponent
|
||||
import com.tangem.features.onramp.component.impl.DefaultOnrampComponent
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
internal interface OnrampComponentModule {
|
||||
|
||||
@Binds
|
||||
@Singleton
|
||||
fun bindOnrampComponentFactory(factory: DefaultOnrampComponent.Factory): OnrampComponent.Factory
|
||||
|
||||
@Binds
|
||||
@Singleton
|
||||
fun bindConfirmResidencyComponentFactory(
|
||||
factory: DefaultConfirmResidencyComponent.Factory,
|
||||
): ConfirmResidencyComponent.Factory
|
||||
}
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
package com.tangem.features.markets.di
|
||||
package com.tangem.features.onramp.di
|
||||
|
||||
import com.tangem.core.featuretoggle.manager.FeatureTogglesManager
|
||||
import com.tangem.features.markets.DefaultMarketsFeatureToggles
|
||||
import com.tangem.features.markets.MarketsFeatureToggles
|
||||
import com.tangem.features.onramp.DefaultOnrampFeatureToggles
|
||||
import com.tangem.features.onramp.OnrampFeatureToggles
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
|
|
@ -11,10 +11,11 @@ import javax.inject.Singleton
|
|||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
internal object FeatureModule {
|
||||
internal object OnrampFeatureModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideFeatureToggles(featureTogglesManager: FeatureTogglesManager): MarketsFeatureToggles =
|
||||
DefaultMarketsFeatureToggles(featureTogglesManager = featureTogglesManager)
|
||||
fun provideFeatureToggles(featureTogglesManager: FeatureTogglesManager): OnrampFeatureToggles {
|
||||
return DefaultOnrampFeatureToggles(featureTogglesManager)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
package com.tangem.features.onramp.di
|
||||
|
||||
import com.tangem.core.decompose.di.DecomposeComponent
|
||||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.features.onramp.model.OnrampModel
|
||||
import com.tangem.features.onramp.model.ConfirmResidencyModel
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.multibindings.ClassKey
|
||||
import dagger.multibindings.IntoMap
|
||||
|
||||
@Module
|
||||
@InstallIn(DecomposeComponent::class)
|
||||
internal interface OnrampModelModule {
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@ClassKey(OnrampModel::class)
|
||||
fun provideOnrampModel(model: OnrampModel): Model
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@ClassKey(ConfirmResidencyModel::class)
|
||||
fun provideConfirmResidencyModel(model: ConfirmResidencyModel): Model
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
package com.tangem.features.onramp.entity
|
||||
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
|
||||
data class ConfirmResidencyUM(
|
||||
val country: String,
|
||||
val countryFlagUrl: String,
|
||||
val isCountrySupported: Boolean,
|
||||
val primaryButtonConfig: ActionButtonConfig,
|
||||
val secondaryButtonConfig: ActionButtonConfig,
|
||||
) {
|
||||
data class ActionButtonConfig(
|
||||
val onClick: () -> Unit,
|
||||
val text: TextReference,
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
package com.tangem.features.onramp.entity
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
internal sealed class OnrampBottomSheetConfig {
|
||||
|
||||
@Serializable
|
||||
data object ConfirmResidency : OnrampBottomSheetConfig()
|
||||
}
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
package com.tangem.features.onramp.model
|
||||
|
||||
import com.tangem.core.decompose.di.ComponentScoped
|
||||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.core.decompose.model.ParamsContainer
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.features.onramp.component.ConfirmResidencyComponent
|
||||
import com.tangem.features.onramp.entity.ConfirmResidencyUM
|
||||
import com.tangem.features.onramp.impl.R
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import javax.inject.Inject
|
||||
|
||||
@ComponentScoped
|
||||
internal class ConfirmResidencyModel @Inject constructor(
|
||||
override val dispatchers: CoroutineDispatcherProvider,
|
||||
paramsContainer: ParamsContainer,
|
||||
) : Model() {
|
||||
|
||||
private val params: ConfirmResidencyComponent.Params = paramsContainer.require()
|
||||
val state: MutableStateFlow<ConfirmResidencyUM> = MutableStateFlow(
|
||||
ConfirmResidencyUM(
|
||||
country = params.countryName,
|
||||
countryFlagUrl = params.countryFlagUrl,
|
||||
isCountrySupported = params.isOnrampSupported,
|
||||
primaryButtonConfig = getPrimaryButtonConfig(),
|
||||
secondaryButtonConfig = ConfirmResidencyUM.ActionButtonConfig(
|
||||
onClick = ::onChangeClick,
|
||||
text = resourceReference(R.string.common_change),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
private fun getPrimaryButtonConfig() = if (params.isOnrampSupported) {
|
||||
ConfirmResidencyUM.ActionButtonConfig(
|
||||
onClick = params.onDismiss,
|
||||
text = resourceReference(R.string.common_confirm),
|
||||
)
|
||||
} else {
|
||||
ConfirmResidencyUM.ActionButtonConfig(
|
||||
onClick = ::onCloseClick,
|
||||
text = resourceReference(R.string.common_close),
|
||||
)
|
||||
}
|
||||
|
||||
@Suppress("EmptyFunctionBlock")
|
||||
private fun onCloseClick() {
|
||||
}
|
||||
|
||||
private fun onChangeClick() {
|
||||
// TODO: [REDACTED_JIRA]
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
package com.tangem.features.onramp.model
|
||||
|
||||
import com.arkivanov.decompose.router.slot.SlotNavigation
|
||||
import com.arkivanov.decompose.router.slot.activate
|
||||
import com.tangem.core.decompose.di.ComponentScoped
|
||||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.core.decompose.model.ParamsContainer
|
||||
import com.tangem.core.decompose.navigation.Router
|
||||
import com.tangem.features.onramp.component.OnrampComponent
|
||||
import com.tangem.features.onramp.entity.OnrampBottomSheetConfig
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
@ComponentScoped
|
||||
internal class OnrampModel @Inject constructor(
|
||||
override val dispatchers: CoroutineDispatcherProvider,
|
||||
private val router: Router,
|
||||
paramsContainer: ParamsContainer,
|
||||
) : Model() {
|
||||
|
||||
@Suppress("UnusedPrivateMember")
|
||||
private val params: OnrampComponent.Params = paramsContainer.require()
|
||||
val bottomSheetNavigation: SlotNavigation<OnrampBottomSheetConfig> = SlotNavigation()
|
||||
|
||||
init {
|
||||
modelScope.launch {
|
||||
delay(1500)
|
||||
bottomSheetNavigation.activate(OnrampBottomSheetConfig.ConfirmResidency)
|
||||
}
|
||||
}
|
||||
|
||||
fun pop() {
|
||||
router.pop()
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
package com.tangem.features.onramp.selecttoken
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import com.tangem.core.decompose.context.AppComponentContext
|
||||
import com.tangem.core.decompose.context.child
|
||||
import com.tangem.features.onramp.selecttoken.ui.OnrampSelectToken
|
||||
import com.tangem.features.onramp.tokenlist.OnrampTokenListComponent
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
|
||||
internal class DefaultOnrampSelectTokenComponent @AssistedInject constructor(
|
||||
@Assisted appComponentContext: AppComponentContext,
|
||||
onrampTokenListComponentFactory: OnrampTokenListComponent.Factory,
|
||||
@Assisted private val params: OnrampSelectTokenComponent.Params,
|
||||
) : AppComponentContext by appComponentContext, OnrampSelectTokenComponent {
|
||||
|
||||
private val onrampTokenListComponent: OnrampTokenListComponent = onrampTokenListComponentFactory.create(
|
||||
context = child(key = "token_list"),
|
||||
params = OnrampTokenListComponent.Params(
|
||||
hasSearchBar = params.hasSearchBar,
|
||||
userWalletId = params.userWalletId,
|
||||
onTokenClick = params.onTokenClick,
|
||||
),
|
||||
)
|
||||
|
||||
@Composable
|
||||
override fun Content(modifier: Modifier) {
|
||||
OnrampSelectToken(
|
||||
titleResId = params.titleResId,
|
||||
onBackClick = router::pop,
|
||||
onrampTokenListComponent = onrampTokenListComponent,
|
||||
modifier = modifier,
|
||||
)
|
||||
}
|
||||
|
||||
@AssistedFactory
|
||||
interface Factory : OnrampSelectTokenComponent.Factory {
|
||||
|
||||
override fun create(
|
||||
context: AppComponentContext,
|
||||
params: OnrampSelectTokenComponent.Params,
|
||||
): DefaultOnrampSelectTokenComponent
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
package com.tangem.features.onramp.selecttoken
|
||||
|
||||
import androidx.annotation.StringRes
|
||||
import com.tangem.core.decompose.factory.ComponentFactory
|
||||
import com.tangem.core.ui.decompose.ComposableContentComponent
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
|
||||
/**
|
||||
* Select token component
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
interface OnrampSelectTokenComponent : ComposableContentComponent {
|
||||
|
||||
interface Factory : ComponentFactory<Params, OnrampSelectTokenComponent>
|
||||
|
||||
/**
|
||||
* Params
|
||||
*
|
||||
* @property hasSearchBar flag that indicates if search bar should be shown
|
||||
* @property userWalletId id of multi-currency wallet
|
||||
* @property titleResId resource id of title
|
||||
* @property onTokenClick callback for token click
|
||||
*/
|
||||
data class Params(
|
||||
val hasSearchBar: Boolean,
|
||||
val userWalletId: UserWalletId,
|
||||
@StringRes val titleResId: Int,
|
||||
val onTokenClick: (CryptoCurrencyStatus) -> Unit,
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
package com.tangem.features.onramp.selecttoken.di
|
||||
|
||||
import com.tangem.features.onramp.selecttoken.DefaultOnrampSelectTokenComponent
|
||||
import com.tangem.features.onramp.selecttoken.OnrampSelectTokenComponent
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
internal interface OnrampSelectTokenComponentModule {
|
||||
|
||||
@Binds
|
||||
@Singleton
|
||||
fun bindOnrampSelectTokenComponentFactory(
|
||||
factory: DefaultOnrampSelectTokenComponent.Factory,
|
||||
): OnrampSelectTokenComponent.Factory
|
||||
}
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
package com.tangem.features.onramp.selecttoken.ui
|
||||
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.imePadding
|
||||
import androidx.compose.foundation.layout.systemBarsPadding
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.tangem.core.ui.components.appbar.AppBarWithBackButton
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.features.onramp.impl.R
|
||||
import com.tangem.features.onramp.tokenlist.OnrampTokenListComponent
|
||||
|
||||
@Composable
|
||||
internal fun OnrampSelectToken(
|
||||
@StringRes titleResId: Int,
|
||||
onBackClick: () -> Unit,
|
||||
onrampTokenListComponent: OnrampTokenListComponent,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
BackHandler(onBack = onBackClick)
|
||||
|
||||
Column(
|
||||
modifier = modifier
|
||||
.background(TangemTheme.colors.background.secondary)
|
||||
.imePadding()
|
||||
.systemBarsPadding(),
|
||||
) {
|
||||
AppBarWithBackButton(
|
||||
onBackClick = onBackClick,
|
||||
text = stringResource(id = titleResId),
|
||||
iconRes = R.drawable.ic_close_24,
|
||||
)
|
||||
|
||||
onrampTokenListComponent.Content(
|
||||
contentPadding = PaddingValues(vertical = 8.dp, horizontal = 16.dp),
|
||||
modifier = Modifier,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
package com.tangem.features.onramp.tokenlist
|
||||
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.Stable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.tangem.core.decompose.context.AppComponentContext
|
||||
import com.tangem.core.decompose.model.getOrCreateModel
|
||||
import com.tangem.features.onramp.tokenlist.model.OnrampTokenListModel
|
||||
import com.tangem.features.onramp.tokenlist.ui.TokenList
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
|
||||
@Stable
|
||||
internal class DefaultOnrampTokenListComponent @AssistedInject constructor(
|
||||
@Assisted context: AppComponentContext,
|
||||
@Assisted params: OnrampTokenListComponent.Params,
|
||||
) : OnrampTokenListComponent, AppComponentContext by context {
|
||||
|
||||
private val model: OnrampTokenListModel = getOrCreateModel(params)
|
||||
|
||||
@Composable
|
||||
override fun Content(contentPadding: PaddingValues, modifier: Modifier) {
|
||||
val state by model.state.collectAsStateWithLifecycle()
|
||||
|
||||
TokenList(state = state, contentPadding = contentPadding, modifier = modifier)
|
||||
}
|
||||
|
||||
@AssistedFactory
|
||||
interface Factory : OnrampTokenListComponent.Factory {
|
||||
override fun create(
|
||||
context: AppComponentContext,
|
||||
params: OnrampTokenListComponent.Params,
|
||||
): DefaultOnrampTokenListComponent
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
package com.tangem.features.onramp.tokenlist
|
||||
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.Stable
|
||||
import androidx.compose.ui.Modifier
|
||||
import com.tangem.core.decompose.factory.ComponentFactory
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
|
||||
/** Token list component that present list of token for multi-currency wallet */
|
||||
@Stable
|
||||
internal interface OnrampTokenListComponent {
|
||||
|
||||
@Composable
|
||||
fun Content(contentPadding: PaddingValues, modifier: Modifier)
|
||||
|
||||
/** Component factory */
|
||||
interface Factory : ComponentFactory<Params, OnrampTokenListComponent>
|
||||
|
||||
/**
|
||||
* Params
|
||||
*
|
||||
* @property hasSearchBar flag that indicates if search bar should be shown
|
||||
* @property userWalletId id of multi-currency wallet
|
||||
* @property onTokenClick callback for token click
|
||||
*/
|
||||
data class Params(
|
||||
val hasSearchBar: Boolean,
|
||||
val userWalletId: UserWalletId,
|
||||
val onTokenClick: (CryptoCurrencyStatus) -> Unit,
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
package com.tangem.features.onramp.tokenlist.di
|
||||
|
||||
import com.tangem.features.onramp.tokenlist.DefaultOnrampTokenListComponent
|
||||
import com.tangem.features.onramp.tokenlist.OnrampTokenListComponent
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
internal interface OnrampTokenListComponentModule {
|
||||
|
||||
@Binds
|
||||
@Singleton
|
||||
fun bindOnrampTokenListComponentFactory(
|
||||
factory: DefaultOnrampTokenListComponent.Factory,
|
||||
): OnrampTokenListComponent.Factory
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
package com.tangem.features.onramp.tokenlist.di
|
||||
|
||||
import com.tangem.core.decompose.di.DecomposeComponent
|
||||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.features.onramp.tokenlist.model.OnrampTokenListModel
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.multibindings.ClassKey
|
||||
import dagger.multibindings.IntoMap
|
||||
|
||||
@Module
|
||||
@InstallIn(DecomposeComponent::class)
|
||||
internal interface OnrampTokenListModelModule {
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@ClassKey(OnrampTokenListModel::class)
|
||||
fun bindOnrampTokenListModel(model: OnrampTokenListModel): Model
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
package com.tangem.features.onramp.tokenlist.entity
|
||||
|
||||
import com.tangem.core.ui.components.tokenlist.state.TokensListItemUM
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
|
||||
/**
|
||||
* Token list UM
|
||||
*
|
||||
* @property items items (search bar, tokens, headers)
|
||||
* @property isBalanceHidden flag that indicates if balance should be hidden
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
internal data class TokenListUM(
|
||||
val items: ImmutableList<TokensListItemUM>,
|
||||
val isBalanceHidden: Boolean,
|
||||
) {
|
||||
|
||||
/** Get search bar if it exists */
|
||||
fun getSearchBar(): TokensListItemUM.SearchBar? {
|
||||
return items.firstOrNull() as? TokensListItemUM.SearchBar
|
||||
}
|
||||
|
||||
/** Get tokens */
|
||||
fun getTokens(): ImmutableList<TokensListItemUM> {
|
||||
if (getSearchBar() == null) return items
|
||||
|
||||
return if (items.size > 1) {
|
||||
items.subList(fromIndex = 1, toIndex = items.size)
|
||||
} else {
|
||||
persistentListOf()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
package com.tangem.features.onramp.tokenlist.entity
|
||||
|
||||
import com.tangem.core.ui.components.tokenlist.state.TokensListItemUM
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.update
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
* [TokenListUM] controller
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
internal class TokenListUMController @Inject constructor() {
|
||||
|
||||
val state: StateFlow<TokenListUM> get() = _state
|
||||
|
||||
private val _state: MutableStateFlow<TokenListUM> = MutableStateFlow(
|
||||
value = TokenListUM(
|
||||
items = persistentListOf(),
|
||||
isBalanceHidden = false,
|
||||
),
|
||||
)
|
||||
|
||||
fun update(transform: (TokenListUM) -> TokenListUM) {
|
||||
Timber.d("Applying non-name transformation")
|
||||
_state.update(transform)
|
||||
}
|
||||
|
||||
fun update(transformer: TokenListUMTransformer) {
|
||||
Timber.d("Applying ${transformer::class.simpleName}")
|
||||
_state.update(transformer::transform)
|
||||
}
|
||||
|
||||
/** Get search bar if it exists */
|
||||
fun getSearchBar(): TokensListItemUM.SearchBar? {
|
||||
return _state.value.getSearchBar()
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
package com.tangem.features.onramp.tokenlist.entity
|
||||
|
||||
import com.tangem.utils.transformer.Transformer
|
||||
|
||||
/**
|
||||
* Base [TokenListUM] transformer
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
internal interface TokenListUMTransformer : Transformer<TokenListUM>
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
package com.tangem.features.onramp.tokenlist.entity.transformer
|
||||
|
||||
import com.tangem.core.ui.components.fields.entity.SearchBarUM
|
||||
import com.tangem.features.onramp.tokenlist.entity.TokenListUM
|
||||
import com.tangem.features.onramp.tokenlist.entity.TokenListUMTransformer
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
|
||||
/**
|
||||
* Base [SearchBarUM] transformer
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
internal abstract class SearchBarUMTransformer : TokenListUMTransformer {
|
||||
|
||||
abstract fun transform(prevState: SearchBarUM): SearchBarUM
|
||||
|
||||
override fun transform(prevState: TokenListUM): TokenListUM {
|
||||
val searchBarItem = prevState.getSearchBar()
|
||||
|
||||
return if (searchBarItem != null) {
|
||||
val updatedSearchBar = searchBarItem.copy(searchBarUM = transform(searchBarItem.searchBarUM))
|
||||
|
||||
prevState.copy(
|
||||
items = persistentListOf(
|
||||
updatedSearchBar,
|
||||
*prevState.getTokens().toTypedArray(),
|
||||
),
|
||||
)
|
||||
} else {
|
||||
prevState
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
package com.tangem.features.onramp.tokenlist.entity.transformer
|
||||
|
||||
import com.tangem.core.ui.components.fields.entity.SearchBarUM
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.features.onramp.impl.R
|
||||
|
||||
internal class UpdateSearchBarActiveStateTransformer(private val isActive: Boolean) : SearchBarUMTransformer() {
|
||||
|
||||
override fun transform(prevState: SearchBarUM): SearchBarUM {
|
||||
val placeholderText = if (isActive) {
|
||||
TextReference.EMPTY
|
||||
} else {
|
||||
resourceReference(id = R.string.common_search)
|
||||
}
|
||||
|
||||
return prevState.copy(placeholderText = placeholderText, isActive = isActive)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
package com.tangem.features.onramp.tokenlist.entity.transformer
|
||||
|
||||
import com.tangem.core.ui.components.fields.entity.SearchBarUM
|
||||
|
||||
internal class UpdateSearchQueryTransformer(private val newQuery: String) : SearchBarUMTransformer() {
|
||||
|
||||
override fun transform(prevState: SearchBarUM): SearchBarUM {
|
||||
return prevState.copy(query = newQuery)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
package com.tangem.features.onramp.tokenlist.entity.transformer
|
||||
|
||||
import com.tangem.common.ui.tokens.TokenItemStateConverter
|
||||
import com.tangem.core.ui.components.fields.entity.SearchBarUM
|
||||
import com.tangem.core.ui.components.tokenlist.state.TokensListItemUM
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
import com.tangem.features.onramp.impl.R
|
||||
import com.tangem.features.onramp.tokenlist.entity.TokenListUM
|
||||
import com.tangem.features.onramp.tokenlist.entity.TokenListUMTransformer
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
|
||||
internal class UpdateTokenItemsTransformer(
|
||||
private val tokenItemStateConverter: TokenItemStateConverter,
|
||||
private val statuses: List<CryptoCurrencyStatus>,
|
||||
private val isBalanceHidden: Boolean,
|
||||
private val hasSearchBar: Boolean,
|
||||
private val onQueryChange: (String) -> Unit,
|
||||
private val onActiveChange: (Boolean) -> Unit,
|
||||
) : TokenListUMTransformer {
|
||||
|
||||
override fun transform(prevState: TokenListUM): TokenListUM {
|
||||
val items = tokenItemStateConverter.convertList(input = statuses).map(TokensListItemUM::Token)
|
||||
|
||||
val searchBarItem = if (hasSearchBar) {
|
||||
prevState.getSearchBar() ?: createSearchBarItem()
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
return prevState.copy(
|
||||
items = (listOfNotNull(searchBarItem) + items).toImmutableList(),
|
||||
isBalanceHidden = isBalanceHidden,
|
||||
)
|
||||
}
|
||||
|
||||
private fun createSearchBarItem(): TokensListItemUM.SearchBar {
|
||||
return TokensListItemUM.SearchBar(
|
||||
searchBarUM = SearchBarUM(
|
||||
placeholderText = resourceReference(id = R.string.common_search),
|
||||
query = "",
|
||||
onQueryChange = onQueryChange,
|
||||
isActive = false,
|
||||
onActiveChange = onActiveChange,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,105 @@
|
|||
package com.tangem.features.onramp.tokenlist.model
|
||||
|
||||
import arrow.core.getOrElse
|
||||
import com.tangem.common.ui.tokens.TokenItemStateConverter
|
||||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.core.decompose.model.ParamsContainer
|
||||
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase
|
||||
import com.tangem.domain.core.utils.getOrElse
|
||||
import com.tangem.domain.tokens.GetTokenListUseCase
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
import com.tangem.domain.tokens.model.TokenList
|
||||
import com.tangem.features.onramp.tokenlist.OnrampTokenListComponent
|
||||
import com.tangem.features.onramp.tokenlist.entity.TokenListUM
|
||||
import com.tangem.features.onramp.tokenlist.entity.TokenListUMController
|
||||
import com.tangem.features.onramp.tokenlist.entity.transformer.UpdateSearchBarActiveStateTransformer
|
||||
import com.tangem.features.onramp.tokenlist.entity.transformer.UpdateSearchQueryTransformer
|
||||
import com.tangem.features.onramp.tokenlist.entity.transformer.UpdateTokenItemsTransformer
|
||||
import com.tangem.features.onramp.tokenlist.utils.SearchTokensManager
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
internal class OnrampTokenListModel @Inject constructor(
|
||||
paramsContainer: ParamsContainer,
|
||||
override val dispatchers: CoroutineDispatcherProvider,
|
||||
private val tokenListUMController: TokenListUMController,
|
||||
private val searchTokensManager: SearchTokensManager,
|
||||
private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
|
||||
private val getBalanceHidingSettingsUseCase: GetBalanceHidingSettingsUseCase,
|
||||
private val getTokenListUseCase: GetTokenListUseCase,
|
||||
) : Model() {
|
||||
|
||||
val state: StateFlow<TokenListUM> = tokenListUMController.state
|
||||
|
||||
private val params: OnrampTokenListComponent.Params = paramsContainer.require()
|
||||
|
||||
init {
|
||||
subscribeOnUpdateState()
|
||||
}
|
||||
|
||||
private fun subscribeOnUpdateState() {
|
||||
combine(
|
||||
flow = getTokenListUseCase.launch(userWalletId = params.userWalletId).distinctUntilChanged(),
|
||||
flow2 = getSelectedAppCurrencyUseCase().map { it.getOrElse { AppCurrency.Default } }.distinctUntilChanged(),
|
||||
flow3 = getBalanceHidingSettingsUseCase().map { it.isBalanceHidden }.distinctUntilChanged(),
|
||||
flow4 = searchTokensManager.query,
|
||||
) { maybeTokenList, appCurrency, isBalanceHidden, query ->
|
||||
val currencies = maybeTokenList.getOrElse(
|
||||
ifLoading = { it ?: TokenList.Empty },
|
||||
ifError = { TokenList.Empty },
|
||||
)
|
||||
.flattenCurrencies()
|
||||
|
||||
val filterTokenList = currencies
|
||||
.filterByQuery(query = query)
|
||||
.filterByAvailability()
|
||||
|
||||
UpdateTokenItemsTransformer(
|
||||
tokenItemStateConverter = TokenItemStateConverter(
|
||||
appCurrency = appCurrency,
|
||||
onItemClick = params.onTokenClick,
|
||||
),
|
||||
statuses = filterTokenList,
|
||||
isBalanceHidden = isBalanceHidden,
|
||||
hasSearchBar = params.hasSearchBar && currencies.isNotEmpty(),
|
||||
onQueryChange = ::onSearchQueryChange,
|
||||
onActiveChange = ::onSearchBarActiveChange,
|
||||
)
|
||||
}
|
||||
.onEach(tokenListUMController::update)
|
||||
.flowOn(dispatchers.main)
|
||||
.launchIn(modelScope)
|
||||
}
|
||||
|
||||
private fun onSearchQueryChange(newQuery: String) {
|
||||
val searchBar = tokenListUMController.getSearchBar()
|
||||
if (searchBar?.searchBarUM?.query == newQuery) return
|
||||
|
||||
modelScope.launch {
|
||||
tokenListUMController.update(transformer = UpdateSearchQueryTransformer(newQuery))
|
||||
|
||||
searchTokensManager.update(newQuery)
|
||||
}
|
||||
}
|
||||
|
||||
private fun onSearchBarActiveChange(isActive: Boolean) {
|
||||
tokenListUMController.update(transformer = UpdateSearchBarActiveStateTransformer(isActive))
|
||||
}
|
||||
|
||||
private fun List<CryptoCurrencyStatus>.filterByQuery(query: String): List<CryptoCurrencyStatus> {
|
||||
return filter {
|
||||
it.currency.name.contains(other = query, ignoreCase = true) ||
|
||||
it.currency.symbol.contains(other = query, ignoreCase = true)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: [REDACTED_JIRA]
|
||||
private fun List<CryptoCurrencyStatus>.filterByAvailability(): List<CryptoCurrencyStatus> {
|
||||
return this
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,65 @@
|
|||
package com.tangem.features.onramp.tokenlist.ui
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.itemsIndexed
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.tangem.core.ui.components.tokenlist.TokenListItem
|
||||
import com.tangem.core.ui.decorations.roundedShapeItemDecoration
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.features.onramp.tokenlist.entity.TokenListUM
|
||||
import com.tangem.features.onramp.tokenlist.ui.preview.PreviewTokenListUMProvider
|
||||
|
||||
/**
|
||||
* Token list
|
||||
*
|
||||
* @param state state
|
||||
* @param modifier modifier
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
@Composable
|
||||
internal fun TokenList(state: TokenListUM, contentPadding: PaddingValues, modifier: Modifier = Modifier) {
|
||||
LazyColumn(modifier = modifier, contentPadding = contentPadding) {
|
||||
itemsIndexed(
|
||||
items = state.items,
|
||||
key = { _, item -> item.id },
|
||||
contentType = { _, item -> item::class.java },
|
||||
itemContent = { index, item ->
|
||||
TokenListItem(
|
||||
state = item,
|
||||
isBalanceHidden = state.isBalanceHidden,
|
||||
modifier = Modifier
|
||||
.animateItem()
|
||||
.roundedShapeItemDecoration(
|
||||
currentIndex = index,
|
||||
lastIndex = state.items.lastIndex,
|
||||
addDefaultPadding = false,
|
||||
backgroundColor = TangemTheme.colors.background.primary,
|
||||
),
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun Preview_TokenList(@PreviewParameter(PreviewTokenListUMProvider::class) state: TokenListUM) {
|
||||
TangemThemePreview {
|
||||
TokenList(
|
||||
state = state,
|
||||
contentPadding = PaddingValues(all = 16.dp),
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.background(color = TangemTheme.colors.background.secondary),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
package com.tangem.features.onramp.tokenlist.ui.preview
|
||||
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||
import com.tangem.core.ui.components.currency.icon.CurrencyIconState
|
||||
import com.tangem.core.ui.components.fields.entity.SearchBarUM
|
||||
import com.tangem.core.ui.components.marketprice.PriceChangeType
|
||||
import com.tangem.core.ui.components.token.state.TokenItemState
|
||||
import com.tangem.core.ui.components.tokenlist.state.TokensListItemUM
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.features.onramp.impl.R
|
||||
import com.tangem.features.onramp.tokenlist.entity.TokenListUM
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
|
||||
internal class PreviewTokenListUMProvider : PreviewParameterProvider<TokenListUM> {
|
||||
|
||||
override val values: Sequence<TokenListUM> = sequenceOf(
|
||||
createTokensList(hasSearchBar = false, createDefaultTokenItem()),
|
||||
createTokensList(hasSearchBar = true, createDefaultTokenItem()),
|
||||
)
|
||||
|
||||
private fun createTokensList(hasSearchBar: Boolean, vararg items: TokensListItemUM): TokenListUM {
|
||||
return TokenListUM(
|
||||
items = buildList {
|
||||
if (hasSearchBar) {
|
||||
TokensListItemUM.SearchBar(
|
||||
searchBarUM = SearchBarUM(
|
||||
placeholderText = resourceReference(id = R.string.common_search),
|
||||
query = "",
|
||||
onQueryChange = {},
|
||||
isActive = false,
|
||||
onActiveChange = {},
|
||||
),
|
||||
)
|
||||
.let { add(element = it) }
|
||||
}
|
||||
|
||||
addAll(items)
|
||||
}
|
||||
.toImmutableList(),
|
||||
isBalanceHidden = false,
|
||||
)
|
||||
}
|
||||
|
||||
private fun createDefaultTokenItem(): TokensListItemUM.Token {
|
||||
return TokensListItemUM.Token(
|
||||
state = TokenItemState.Content(
|
||||
id = "1",
|
||||
iconState = CurrencyIconState.Locked,
|
||||
titleState = TokenItemState.TitleState.Content(text = "Bitcoin"),
|
||||
fiatAmountState = TokenItemState.FiatAmountState.Content(text = "12 368,14 \$"),
|
||||
subtitle2State = TokenItemState.Subtitle2State.TextContent(text = "0,35853044 BTC"),
|
||||
subtitleState = TokenItemState.SubtitleState.CryptoPriceContent(
|
||||
price = "34 496,75 \$",
|
||||
priceChangePercent = "0,43 %",
|
||||
type = PriceChangeType.DOWN,
|
||||
),
|
||||
onItemClick = {},
|
||||
onItemLongClick = {},
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
package com.tangem.features.onramp.tokenlist.utils
|
||||
|
||||
import com.tangem.utils.coroutines.JobHolder
|
||||
import com.tangem.utils.coroutines.withDebounce
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
* Search tokens manager
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
internal class SearchTokensManager @Inject constructor() {
|
||||
|
||||
val query: Flow<String>
|
||||
get() = _query
|
||||
|
||||
private val _query = MutableStateFlow(value = "")
|
||||
|
||||
private val jobHolder = JobHolder()
|
||||
|
||||
suspend fun update(value: String) {
|
||||
coroutineScope {
|
||||
if (value.isEmpty()) {
|
||||
_query.value = value
|
||||
} else {
|
||||
withDebounce(jobHolder) { _query.value = value }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,165 @@
|
|||
package com.tangem.features.onramp.ui
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||
import coil.compose.SubcomposeAsyncImage
|
||||
import coil.request.ImageRequest
|
||||
import com.tangem.core.ui.components.CircleShimmer
|
||||
import com.tangem.core.ui.components.PrimaryButton
|
||||
import com.tangem.core.ui.components.SecondaryButton
|
||||
import com.tangem.core.ui.components.appbar.TangemTopAppBar
|
||||
import com.tangem.core.ui.components.appbar.TangemTopAppBarHeight
|
||||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheet
|
||||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
|
||||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent
|
||||
import com.tangem.core.ui.extensions.resolveReference
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.features.onramp.component.ConfirmResidencyComponent
|
||||
import com.tangem.features.onramp.component.preview.PreviewConfirmResidencyComponent
|
||||
import com.tangem.features.onramp.entity.ConfirmResidencyUM
|
||||
import com.tangem.features.onramp.impl.R
|
||||
|
||||
@Composable
|
||||
internal fun ConfirmResidencyBottomSheet(config: TangemBottomSheetConfig, content: @Composable (Modifier) -> Unit) {
|
||||
TangemBottomSheet<TangemBottomSheetConfigContent.Empty>(
|
||||
config = config,
|
||||
addBottomInsets = true,
|
||||
containerColor = TangemTheme.colors.background.tertiary,
|
||||
title = { _ -> Title() },
|
||||
content = {
|
||||
val contentModifier = Modifier
|
||||
.padding(horizontal = TangemTheme.dimens.spacing16)
|
||||
.padding(bottom = TangemTheme.dimens.spacing16)
|
||||
.fillMaxWidth()
|
||||
.wrapContentHeight()
|
||||
|
||||
content(contentModifier)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun Title(modifier: Modifier = Modifier) {
|
||||
TangemTopAppBar(
|
||||
modifier = modifier,
|
||||
title = resourceReference(R.string.onramp_residency_bottomsheet_title),
|
||||
titleAlignment = Alignment.CenterHorizontally,
|
||||
height = TangemTopAppBarHeight.BOTTOM_SHEET,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
internal fun ConfirmResidencyBottomSheetContent(model: ConfirmResidencyUM, modifier: Modifier = Modifier) {
|
||||
Column(modifier = modifier) {
|
||||
CountryContent(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(vertical = TangemTheme.dimens.spacing24),
|
||||
name = model.country,
|
||||
flagUrl = model.countryFlagUrl,
|
||||
isCountrySupported = model.isCountrySupported,
|
||||
)
|
||||
SecondaryButton(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
text = model.secondaryButtonConfig.text.resolveReference(),
|
||||
onClick = model.secondaryButtonConfig.onClick,
|
||||
)
|
||||
PrimaryButton(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(top = TangemTheme.dimens.spacing12),
|
||||
text = model.primaryButtonConfig.text.resolveReference(),
|
||||
onClick = model.primaryButtonConfig.onClick,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun CountryContent(name: String, flagUrl: String, isCountrySupported: Boolean, modifier: Modifier = Modifier) {
|
||||
Column(
|
||||
modifier = modifier,
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
val flagSize = with(LocalDensity.current) { TangemTheme.dimens.size36.roundToPx() }
|
||||
SubcomposeAsyncImage(
|
||||
modifier = Modifier.size(TangemTheme.dimens.size36),
|
||||
model = ImageRequest.Builder(LocalContext.current)
|
||||
.size(size = flagSize)
|
||||
.data(flagUrl)
|
||||
.memoryCacheKey(flagUrl + flagSize)
|
||||
.crossfade(true)
|
||||
.allowHardware(false)
|
||||
.build(),
|
||||
loading = { CircleShimmer() },
|
||||
contentDescription = null,
|
||||
)
|
||||
|
||||
Text(
|
||||
modifier = Modifier.padding(top = TangemTheme.dimens.spacing12),
|
||||
text = name,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
style = TangemTheme.typography.subtitle1,
|
||||
maxLines = 1,
|
||||
)
|
||||
|
||||
Text(
|
||||
modifier = Modifier.padding(top = TangemTheme.dimens.spacing6),
|
||||
text = stringResource(
|
||||
id = if (isCountrySupported) {
|
||||
R.string.onramp_residency_bottomsheet_country_subtitle
|
||||
} else {
|
||||
R.string.onramp_residency_bottomsheet_country_not_supported
|
||||
},
|
||||
),
|
||||
color = if (isCountrySupported) TangemTheme.colors.text.tertiary else TangemTheme.colors.text.warning,
|
||||
style = TangemTheme.typography.body2,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// region Preview
|
||||
@Composable
|
||||
@Preview(showBackground = true)
|
||||
@Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
private fun Preview_ConfirmResidencyBottomSheet(
|
||||
@PreviewParameter(ConfirmResidencyComponentPreviewProvider::class) component: ConfirmResidencyComponent,
|
||||
) {
|
||||
TangemThemePreview {
|
||||
component.BottomSheet()
|
||||
}
|
||||
}
|
||||
|
||||
private class ConfirmResidencyComponentPreviewProvider : PreviewParameterProvider<ConfirmResidencyComponent> {
|
||||
override val values: Sequence<ConfirmResidencyComponent>
|
||||
get() = sequenceOf(
|
||||
PreviewConfirmResidencyComponent(),
|
||||
PreviewConfirmResidencyComponent(
|
||||
initialState = ConfirmResidencyUM(
|
||||
country = "Russia",
|
||||
countryFlagUrl = "https://hatscripts.github.io/circle-flags/flags/ru.svg",
|
||||
isCountrySupported = false,
|
||||
primaryButtonConfig = ConfirmResidencyUM.ActionButtonConfig(
|
||||
onClick = {},
|
||||
text = stringReference("Close"),
|
||||
),
|
||||
secondaryButtonConfig = ConfirmResidencyUM.ActionButtonConfig(
|
||||
onClick = {},
|
||||
text = stringReference("Change"),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
@ -4,6 +4,8 @@ import com.tangem.common.extensions.isZero
|
|||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.core.ui.extensions.wrappedList
|
||||
import com.tangem.core.ui.format.bigdecimal.crypto
|
||||
import com.tangem.core.ui.format.bigdecimal.format
|
||||
import com.tangem.core.ui.utils.DateTimeFormatters
|
||||
import com.tangem.core.ui.utils.toDateFormatWithTodayYesterday
|
||||
import com.tangem.core.ui.utils.toTimeFormat
|
||||
|
|
@ -17,7 +19,6 @@ import com.tangem.features.send.impl.presentation.state.recipient.utils.RECENT_K
|
|||
import com.tangem.features.send.impl.presentation.state.recipient.utils.emptyListState
|
||||
import com.tangem.utils.Provider
|
||||
import com.tangem.utils.converter.Converter
|
||||
import com.tangem.utils.toFormattedCurrencyString
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.toPersistentList
|
||||
|
||||
|
|
@ -76,10 +77,7 @@ internal class SendRecipientHistoryListConverter(
|
|||
}
|
||||
|
||||
private fun TxHistoryItem.getAmount(cryptoCurrency: CryptoCurrency): String {
|
||||
return amount.toFormattedCurrencyString(
|
||||
currency = cryptoCurrency.symbol,
|
||||
decimals = cryptoCurrency.decimals,
|
||||
)
|
||||
return amount.format { crypto(cryptoCurrency) }
|
||||
}
|
||||
|
||||
private fun TxHistoryItem.extractTimestamp(): TextReference {
|
||||
|
|
|
|||
|
|
@ -32,6 +32,9 @@ import com.tangem.core.ui.components.buttons.common.TangemButtonIconPosition
|
|||
import com.tangem.core.ui.components.buttons.common.TangemButtonsDefaults
|
||||
import com.tangem.core.ui.components.keyboardAsState
|
||||
import com.tangem.core.ui.extensions.*
|
||||
import com.tangem.core.ui.format.bigdecimal.crypto
|
||||
import com.tangem.core.ui.format.bigdecimal.fee
|
||||
import com.tangem.core.ui.format.bigdecimal.format
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.utils.BigDecimalFormatter
|
||||
import com.tangem.features.send.impl.presentation.state.SendStates
|
||||
|
|
@ -228,12 +231,14 @@ private fun SendStates.FeeState.getFiatValue() = if (isFeeConvertibleToFiat) {
|
|||
)
|
||||
} else {
|
||||
val amount = fee?.amount
|
||||
BigDecimalFormatter.formatCryptoFeeAmount(
|
||||
cryptoAmount = amount?.value,
|
||||
cryptoCurrency = amount?.currencySymbol.orEmpty(),
|
||||
decimals = amount?.decimals ?: 0,
|
||||
canBeLower = isFeeApproximate,
|
||||
)
|
||||
amount?.value.format {
|
||||
crypto(
|
||||
decimals = amount?.decimals ?: 0,
|
||||
symbol = amount?.currencySymbol.orEmpty(),
|
||||
).fee(
|
||||
canBeLower = isFeeApproximate,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getButtonData(
|
||||
|
|
|
|||
|
|
@ -16,6 +16,9 @@ import com.tangem.core.ui.components.RectangleShimmer
|
|||
import com.tangem.core.ui.components.SpacerWMax
|
||||
import com.tangem.core.ui.components.rows.SelectorRowItem
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.core.ui.format.bigdecimal.crypto
|
||||
import com.tangem.core.ui.format.bigdecimal.fee
|
||||
import com.tangem.core.ui.format.bigdecimal.format
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.utils.BigDecimalFormatter
|
||||
import com.tangem.core.ui.utils.parseToBigDecimal
|
||||
|
|
@ -53,12 +56,12 @@ internal fun SendSpeedSelectorItem(
|
|||
onSelect = onSelect,
|
||||
modifier = modifier,
|
||||
preDot = stringReference(
|
||||
BigDecimalFormatter.formatCryptoFeeAmount(
|
||||
cryptoAmount = amount?.value,
|
||||
cryptoCurrency = amount?.currencySymbol.orEmpty(),
|
||||
decimals = amount?.decimals ?: 0,
|
||||
canBeLower = state.isFeeApproximate,
|
||||
),
|
||||
amount?.value.format {
|
||||
crypto(
|
||||
symbol = amount?.currencySymbol.orEmpty(),
|
||||
decimals = amount?.decimals ?: 0,
|
||||
).fee(canBeLower = state.isFeeApproximate)
|
||||
},
|
||||
),
|
||||
postDot = if (state.isFeeConvertibleToFiat) {
|
||||
getFiatReference(amount?.value, state.rate, state.appCurrency)
|
||||
|
|
|
|||
|
|
@ -18,6 +18,9 @@ import com.tangem.common.ui.amountScreen.utils.getFiatReference
|
|||
import com.tangem.core.ui.components.RectangleShimmer
|
||||
import com.tangem.core.ui.components.rows.SelectorRowItem
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.core.ui.format.bigdecimal.crypto
|
||||
import com.tangem.core.ui.format.bigdecimal.fee
|
||||
import com.tangem.core.ui.format.bigdecimal.format
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.core.ui.utils.BigDecimalFormatter
|
||||
|
|
@ -62,12 +65,12 @@ internal fun FeeBlock(feeState: SendStates.FeeState, isClickDisabled: Boolean, o
|
|||
titleRes = title,
|
||||
iconRes = icon,
|
||||
preDot = stringReference(
|
||||
BigDecimalFormatter.formatCryptoFeeAmount(
|
||||
cryptoAmount = feeAmount?.value,
|
||||
cryptoCurrency = feeAmount?.currencySymbol.orEmpty(),
|
||||
decimals = feeAmount?.decimals ?: 0,
|
||||
canBeLower = feeState.isFeeApproximate,
|
||||
),
|
||||
feeAmount?.value.format {
|
||||
crypto(
|
||||
symbol = feeAmount?.currencySymbol.orEmpty(),
|
||||
decimals = feeAmount?.decimals ?: 0,
|
||||
).fee(canBeLower = feeState.isFeeApproximate)
|
||||
},
|
||||
),
|
||||
postDot = if (feeState.isFeeConvertibleToFiat) {
|
||||
getFiatReference(feeAmount?.value, feeState.rate, feeState.appCurrency)
|
||||
|
|
|
|||
|
|
@ -32,10 +32,10 @@ internal class StakingAnalyticSender(
|
|||
|
||||
fun confirmationScreen(value: StakingUiState) {
|
||||
val confirmationState = value.confirmationState as? StakingStates.ConfirmationState.Data
|
||||
val validatorState = confirmationState?.validatorState as? ValidatorState.Content
|
||||
val validatorState = value.validatorState as? StakingStates.ValidatorState.Data
|
||||
val validatorName = validatorState?.chosenValidator?.name ?: return
|
||||
|
||||
if (confirmationState.innerState == InnerConfirmationStakingState.COMPLETED) return
|
||||
if (confirmationState?.innerState == InnerConfirmationStakingState.COMPLETED) return
|
||||
|
||||
analyticsEventHandler.send(
|
||||
StakingAnalyticsEvent.ConfirmationScreenOpened(
|
||||
|
|
@ -53,6 +53,7 @@ internal class StakingAnalyticSender(
|
|||
StakingStep.Amount -> StakeScreenSource.Amount
|
||||
StakingStep.Confirmation -> StakeScreenSource.Confirmation
|
||||
StakingStep.Validators,
|
||||
StakingStep.RestakeValidator,
|
||||
StakingStep.RewardsValidators,
|
||||
-> StakeScreenSource.Validators
|
||||
},
|
||||
|
|
@ -75,8 +76,7 @@ internal class StakingAnalyticSender(
|
|||
}
|
||||
|
||||
fun sendTransactionStakingAnalytics(value: StakingUiState) {
|
||||
val confirmationState = value.confirmationState as? StakingStates.ConfirmationState.Data
|
||||
val validatorState = confirmationState?.validatorState as? ValidatorState.Content
|
||||
val validatorState = value.validatorState as? StakingStates.ValidatorState.Data
|
||||
val validatorName = validatorState?.chosenValidator?.name ?: return
|
||||
|
||||
analyticsEventHandler.send(
|
||||
|
|
@ -98,8 +98,7 @@ internal class StakingAnalyticSender(
|
|||
}
|
||||
|
||||
fun sendTransactionStakingClickedAnalytics(value: StakingUiState) {
|
||||
val confirmationState = value.confirmationState as? StakingStates.ConfirmationState.Data
|
||||
val validatorState = confirmationState?.validatorState as? ValidatorState.Content
|
||||
val validatorState = value.validatorState as? StakingStates.ValidatorState.Data
|
||||
val validatorName = validatorState?.chosenValidator?.name ?: return
|
||||
|
||||
analyticsEventHandler.send(
|
||||
|
|
@ -114,11 +113,9 @@ internal class StakingAnalyticSender(
|
|||
val confirmationState = value.confirmationState as? StakingStates.ConfirmationState.Data
|
||||
|
||||
return when (value.actionType) {
|
||||
StakingActionCommonType.ENTER -> StakingActionType.STAKE
|
||||
StakingActionCommonType.EXIT -> StakingActionType.UNSTAKE
|
||||
StakingActionCommonType.PENDING_REWARDS,
|
||||
StakingActionCommonType.PENDING_OTHER,
|
||||
-> confirmationState?.pendingAction?.type ?: StakingActionType.UNKNOWN
|
||||
StakingActionCommonType.Enter -> StakingActionType.STAKE
|
||||
StakingActionCommonType.Exit -> StakingActionType.UNSTAKE
|
||||
is StakingActionCommonType.Pending -> confirmationState?.pendingAction?.type ?: StakingActionType.UNKNOWN
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -45,6 +45,12 @@ internal class StakingStateController @Inject constructor(
|
|||
mutableUiState.update(function = titleTransformer::transform)
|
||||
}
|
||||
|
||||
fun updateAll(vararg transformer: Transformer<StakingUiState>) {
|
||||
transformer.forEach { mutableUiState.update(function = it::transform) }
|
||||
mutableUiState.update(function = buttonsTransformer::transform)
|
||||
mutableUiState.update(function = titleTransformer::transform)
|
||||
}
|
||||
|
||||
fun clear() {
|
||||
mutableUiState.update { getInitialState() }
|
||||
mutableUiState.update(function = buttonsTransformer::transform)
|
||||
|
|
@ -72,12 +78,13 @@ internal class StakingStateController @Inject constructor(
|
|||
currentStep = StakingStep.InitialInfo,
|
||||
initialInfoState = StakingStates.InitialInfoState.Empty(),
|
||||
amountState = AmountState.Empty(),
|
||||
validatorState = StakingStates.ValidatorState.Empty(),
|
||||
rewardsValidatorsState = StakingStates.RewardsValidatorsState.Empty(),
|
||||
confirmationState = StakingStates.ConfirmationState.Empty(),
|
||||
isBalanceHidden = false,
|
||||
event = consumedEvent(),
|
||||
bottomSheetConfig = null,
|
||||
actionType = StakingActionCommonType.ENTER,
|
||||
actionType = StakingActionCommonType.Enter,
|
||||
buttonsState = NavigationButtonsState.Empty,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,12 +23,14 @@ internal class StakingStateRouter(
|
|||
fun onNextClick() {
|
||||
when (stateController.value.currentStep) {
|
||||
StakingStep.InitialInfo -> when (stateController.value.actionType) {
|
||||
StakingActionCommonType.ENTER -> showAmount()
|
||||
StakingActionCommonType.PENDING_OTHER,
|
||||
StakingActionCommonType.EXIT,
|
||||
StakingActionCommonType.Enter -> showAmount()
|
||||
StakingActionCommonType.Pending.Other,
|
||||
StakingActionCommonType.Exit,
|
||||
StakingActionCommonType.Pending.Rewards,
|
||||
-> showConfirmation()
|
||||
StakingActionCommonType.PENDING_REWARDS -> showRewardsValidators()
|
||||
StakingActionCommonType.Pending.Restake -> showRestakeValidators()
|
||||
}
|
||||
StakingStep.RestakeValidator,
|
||||
StakingStep.RewardsValidators,
|
||||
StakingStep.Validators,
|
||||
StakingStep.Amount,
|
||||
|
|
@ -41,25 +43,31 @@ internal class StakingStateRouter(
|
|||
val uiState = stateController.uiState.value
|
||||
when (uiState.currentStep) {
|
||||
StakingStep.InitialInfo -> onBackClick()
|
||||
StakingStep.Amount -> showInitial()
|
||||
StakingStep.RestakeValidator,
|
||||
StakingStep.RewardsValidators,
|
||||
StakingStep.Amount,
|
||||
-> showInitial()
|
||||
StakingStep.Confirmation -> {
|
||||
if (uiState.actionType != StakingActionCommonType.ENTER) {
|
||||
if (uiState.actionType != StakingActionCommonType.Enter) {
|
||||
showInitial()
|
||||
} else {
|
||||
showAmount()
|
||||
}
|
||||
}
|
||||
StakingStep.Validators -> showConfirmation()
|
||||
StakingStep.RewardsValidators -> showInitial()
|
||||
}
|
||||
}
|
||||
|
||||
fun showValidators() {
|
||||
stateController.update { it.copy(currentStep = StakingStep.Validators) }
|
||||
}
|
||||
|
||||
private fun showInitial() {
|
||||
analyticSender.initialInfoScreen(stateController.value)
|
||||
stateController.update { it.copy(currentStep = StakingStep.InitialInfo) }
|
||||
}
|
||||
|
||||
private fun showRewardsValidators() {
|
||||
fun showRewardsValidators() {
|
||||
analyticsEventsHandler.send(StakingAnalyticsEvent.RewardScreenOpened)
|
||||
stateController.update { it.copy(currentStep = StakingStep.RewardsValidators) }
|
||||
}
|
||||
|
|
@ -69,8 +77,8 @@ internal class StakingStateRouter(
|
|||
stateController.update { it.copy(currentStep = StakingStep.Amount) }
|
||||
}
|
||||
|
||||
fun showValidators() {
|
||||
stateController.update { it.copy(currentStep = StakingStep.Validators) }
|
||||
private fun showRestakeValidators() {
|
||||
stateController.update { it.copy(currentStep = StakingStep.RestakeValidator) }
|
||||
}
|
||||
|
||||
private fun showConfirmation() {
|
||||
|
|
|
|||
|
|
@ -9,8 +9,8 @@ import com.tangem.core.ui.components.list.RoundedListWithDividersItemData
|
|||
import com.tangem.core.ui.event.StateEvent
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.pullToRefresh.PullToRefreshConfig
|
||||
import com.tangem.domain.staking.model.PendingTransaction
|
||||
import com.tangem.domain.staking.model.stakekit.PendingAction
|
||||
import com.tangem.domain.staking.model.stakekit.Yield
|
||||
import com.tangem.domain.staking.model.stakekit.action.StakingActionCommonType
|
||||
import com.tangem.features.staking.impl.presentation.state.bottomsheet.InfoType
|
||||
import com.tangem.features.staking.impl.presentation.state.events.StakingEvent
|
||||
|
|
@ -34,6 +34,7 @@ internal data class StakingUiState(
|
|||
val amountState: AmountState,
|
||||
val rewardsValidatorsState: StakingStates.RewardsValidatorsState,
|
||||
val confirmationState: StakingStates.ConfirmationState,
|
||||
val validatorState: StakingStates.ValidatorState,
|
||||
val isBalanceHidden: Boolean,
|
||||
val bottomSheetConfig: TangemBottomSheetConfig?,
|
||||
val actionType: StakingActionCommonType,
|
||||
|
|
@ -45,10 +46,12 @@ internal data class StakingUiState(
|
|||
initialInfoState: StakingStates.InitialInfoState = this.initialInfoState,
|
||||
amountState: AmountState = this.amountState,
|
||||
confirmationState: StakingStates.ConfirmationState = this.confirmationState,
|
||||
validatorState: StakingStates.ValidatorState = this.validatorState,
|
||||
): StakingUiState = copy(
|
||||
initialInfoState = initialInfoState,
|
||||
amountState = amountState,
|
||||
confirmationState = confirmationState,
|
||||
validatorState = validatorState,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -85,13 +88,30 @@ internal sealed class StakingStates {
|
|||
) : RewardsValidatorsState()
|
||||
}
|
||||
|
||||
sealed class ValidatorState : StakingStates() {
|
||||
abstract val isClickable: Boolean
|
||||
|
||||
data class Data(
|
||||
override val isPrimaryButtonEnabled: Boolean,
|
||||
override val isClickable: Boolean,
|
||||
val isVisibleOnConfirmation: Boolean,
|
||||
val chosenValidator: Yield.Validator,
|
||||
val activeValidator: Yield.Validator?,
|
||||
val availableValidators: List<Yield.Validator>,
|
||||
) : ValidatorState()
|
||||
|
||||
data class Empty(
|
||||
override val isClickable: Boolean = false,
|
||||
override val isPrimaryButtonEnabled: Boolean = false,
|
||||
) : ValidatorState()
|
||||
}
|
||||
|
||||
/** Confirmation state */
|
||||
sealed class ConfirmationState : StakingStates() {
|
||||
data class Data(
|
||||
override val isPrimaryButtonEnabled: Boolean,
|
||||
val innerState: InnerConfirmationStakingState,
|
||||
val feeState: FeeState,
|
||||
val validatorState: ValidatorState,
|
||||
val pendingAction: PendingAction?,
|
||||
val pendingActions: ImmutableList<PendingAction>?,
|
||||
val notifications: ImmutableList<NotificationUM>,
|
||||
|
|
@ -99,7 +119,6 @@ internal sealed class StakingStates {
|
|||
val transactionDoneState: TransactionDoneState,
|
||||
val isApprovalNeeded: Boolean,
|
||||
val reduceAmountBy: BigDecimal?,
|
||||
val possiblePendingTransaction: PendingTransaction?,
|
||||
) : ConfirmationState()
|
||||
|
||||
data class Empty(
|
||||
|
|
@ -112,6 +131,7 @@ enum class StakingStep {
|
|||
InitialInfo,
|
||||
RewardsValidators,
|
||||
Amount,
|
||||
RestakeValidator,
|
||||
Confirmation,
|
||||
Validators,
|
||||
}
|
||||
|
|
@ -1,34 +0,0 @@
|
|||
package com.tangem.features.staking.impl.presentation.state
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import com.tangem.domain.staking.model.stakekit.Yield
|
||||
|
||||
@Immutable
|
||||
internal sealed class ValidatorState {
|
||||
|
||||
abstract val isClickable: Boolean
|
||||
|
||||
data class Content(
|
||||
override val isClickable: Boolean,
|
||||
val chosenValidator: Yield.Validator,
|
||||
val availableValidators: List<Yield.Validator>,
|
||||
) : ValidatorState()
|
||||
|
||||
data object Loading : ValidatorState() {
|
||||
override val isClickable: Boolean
|
||||
get() = false
|
||||
}
|
||||
|
||||
data object Error : ValidatorState() {
|
||||
override val isClickable: Boolean
|
||||
get() = false
|
||||
}
|
||||
|
||||
fun copySealed(isClickable: Boolean): ValidatorState {
|
||||
return if (this is Content) {
|
||||
copy(isClickable = isClickable)
|
||||
} else {
|
||||
this
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,8 @@
|
|||
package com.tangem.features.staking.impl.presentation.state.converters
|
||||
|
||||
import com.tangem.core.ui.extensions.*
|
||||
import com.tangem.core.ui.format.bigdecimal.crypto
|
||||
import com.tangem.core.ui.format.bigdecimal.format
|
||||
import com.tangem.core.ui.utils.BigDecimalFormatter
|
||||
import com.tangem.core.ui.utils.parseBigDecimal
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
|
|
@ -47,10 +49,7 @@ internal class BalanceItemConverter(
|
|||
cryptoValue = cryptoAmount.parseBigDecimal(cryptoCurrency.decimals),
|
||||
cryptoDecimal = cryptoAmount,
|
||||
cryptoAmount = stringReference(
|
||||
BigDecimalFormatter.formatCryptoAmount(
|
||||
cryptoAmount = cryptoAmount,
|
||||
cryptoCurrency = cryptoCurrency,
|
||||
),
|
||||
cryptoAmount.format { crypto(cryptoCurrency) },
|
||||
),
|
||||
fiatAmount = stringReference(
|
||||
BigDecimalFormatter.formatFiatAmount(
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
package com.tangem.features.staking.impl.presentation.state.converters
|
||||
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.core.ui.format.bigdecimal.crypto
|
||||
import com.tangem.core.ui.format.bigdecimal.format
|
||||
import com.tangem.core.ui.utils.BigDecimalFormatter
|
||||
import com.tangem.core.ui.utils.parseBigDecimal
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
|
|
@ -65,10 +67,9 @@ internal class RewardsValidatorStateConverter(
|
|||
val appCurrency = appCurrencyProvider()
|
||||
val cryptoCurrency = cryptoCurrencyStatus.currency
|
||||
val cryptoAmount = stringReference(
|
||||
BigDecimalFormatter.formatCryptoAmount(
|
||||
cryptoAmount = cryptoValue,
|
||||
cryptoCurrency = cryptoCurrency,
|
||||
),
|
||||
cryptoValue.format {
|
||||
crypto(cryptoCurrency)
|
||||
},
|
||||
)
|
||||
val fiatAmount = stringReference(
|
||||
BigDecimalFormatter.formatFiatAmount(
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
package com.tangem.features.staking.impl.presentation.state.converters
|
||||
|
||||
import com.tangem.common.extensions.isZero
|
||||
import com.tangem.core.ui.format.bigdecimal.crypto
|
||||
import com.tangem.core.ui.format.bigdecimal.format
|
||||
import com.tangem.core.ui.utils.BigDecimalFormatter
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.staking.model.stakekit.*
|
||||
|
|
@ -41,10 +43,7 @@ internal class YieldBalancesConverter(
|
|||
}
|
||||
val (type, isActionable) = getRewardBlockType()
|
||||
InnerYieldBalanceState.Data(
|
||||
rewardsCrypto = BigDecimalFormatter.formatCryptoAmount(
|
||||
cryptoAmount = cryptoRewardsValue,
|
||||
cryptoCurrency = cryptoCurrency,
|
||||
),
|
||||
rewardsCrypto = cryptoRewardsValue.format { crypto(cryptoCurrency) },
|
||||
rewardsFiat = BigDecimalFormatter.formatFiatAmount(
|
||||
fiatAmount = fiatRewardsValue,
|
||||
fiatCurrencyCode = appCurrency.code,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,9 @@
|
|||
package com.tangem.features.staking.impl.presentation.state.helpers
|
||||
|
||||
import com.tangem.domain.staking.FetchStakingYieldBalanceUseCase
|
||||
import com.tangem.domain.staking.FetchActionsUseCase
|
||||
import com.tangem.domain.staking.model.stakekit.Yield
|
||||
import com.tangem.domain.staking.model.stakekit.action.StakingActionStatus
|
||||
import com.tangem.domain.tokens.FetchPendingTransactionsUseCase
|
||||
import com.tangem.domain.tokens.UpdateDelayedNetworkStatusUseCase
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
|
|
@ -20,16 +23,21 @@ internal class StakingBalanceUpdater @AssistedInject constructor(
|
|||
private val stakingYieldBalanceUseCase: FetchStakingYieldBalanceUseCase,
|
||||
private val getTxHistoryItemsCountUseCase: GetTxHistoryItemsCountUseCase,
|
||||
private val getTxHistoryItemsUseCase: GetTxHistoryItemsUseCase,
|
||||
private val fetchActionsUseCase: FetchActionsUseCase,
|
||||
@DelayedWork private val coroutineScope: CoroutineScope,
|
||||
@Assisted private val userWallet: UserWallet,
|
||||
@Assisted private val cryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||
@Assisted private val yield: Yield,
|
||||
) {
|
||||
fun scheduleUpdates() {
|
||||
fun fullUpdate() {
|
||||
coroutineScope.launch {
|
||||
listOf(
|
||||
// we should update network to find pending tx after 1 sec
|
||||
async {
|
||||
fetchPendingTransactionsUseCase(userWallet.walletId, setOf(cryptoCurrencyStatus.currency.network))
|
||||
fetchPendingTransactionsUseCase(
|
||||
userWalletId = userWallet.walletId,
|
||||
networks = setOf(cryptoCurrencyStatus.currency.network),
|
||||
)
|
||||
},
|
||||
// we should update tx history and network for new balances
|
||||
async {
|
||||
|
|
@ -41,11 +49,14 @@ internal class StakingBalanceUpdater @AssistedInject constructor(
|
|||
async {
|
||||
updateNetworkStatuses()
|
||||
},
|
||||
async {
|
||||
updateProcessingActions()
|
||||
},
|
||||
).awaitAll()
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun instantUpdate() {
|
||||
suspend fun partialUpdate() {
|
||||
coroutineScope {
|
||||
listOf(
|
||||
async {
|
||||
|
|
@ -54,6 +65,19 @@ internal class StakingBalanceUpdater @AssistedInject constructor(
|
|||
async {
|
||||
updateNetworkStatuses(delay = 0)
|
||||
},
|
||||
async {
|
||||
updateProcessingActions()
|
||||
},
|
||||
).awaitAll()
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun initialUpdate() {
|
||||
coroutineScope {
|
||||
listOf(
|
||||
async {
|
||||
updateStakeBalance()
|
||||
},
|
||||
).awaitAll()
|
||||
}
|
||||
}
|
||||
|
|
@ -91,9 +115,22 @@ internal class StakingBalanceUpdater @AssistedInject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private suspend fun updateProcessingActions() {
|
||||
fetchActionsUseCase(
|
||||
userWalletId = userWallet.walletId,
|
||||
cryptoCurrency = cryptoCurrencyStatus.currency,
|
||||
networkType = yield.token.network,
|
||||
stakingActionStatus = StakingActionStatus.PROCESSING,
|
||||
)
|
||||
}
|
||||
|
||||
@AssistedFactory
|
||||
interface Factory {
|
||||
fun create(cryptoCurrencyStatus: CryptoCurrencyStatus, userWallet: UserWallet): StakingBalanceUpdater
|
||||
fun create(
|
||||
cryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||
userWallet: UserWallet,
|
||||
yield: Yield,
|
||||
): StakingBalanceUpdater
|
||||
}
|
||||
|
||||
private companion object {
|
||||
|
|
|
|||
|
|
@ -22,8 +22,7 @@ import com.tangem.domain.transaction.usecase.GetFeeUseCase
|
|||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.features.staking.impl.presentation.state.StakingStateController
|
||||
import com.tangem.features.staking.impl.presentation.state.StakingStates
|
||||
import com.tangem.features.staking.impl.presentation.state.ValidatorState
|
||||
import com.tangem.features.staking.impl.presentation.state.utils.isSolanaWithdraw
|
||||
import com.tangem.features.staking.impl.presentation.state.utils.isComposePendingActions
|
||||
import com.tangem.utils.extensions.orZero
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
|
|
@ -48,8 +47,6 @@ internal class StakingFeeTransactionLoader @AssistedInject constructor(
|
|||
) {
|
||||
|
||||
suspend fun getFee(
|
||||
pendingAction: PendingAction?,
|
||||
pendingActions: ImmutableList<PendingAction>?,
|
||||
onStakingFee: (Fee) -> Unit,
|
||||
onStakingFeeError: (StakingError) -> Unit,
|
||||
onApprovalFee: (TransactionFee) -> Unit,
|
||||
|
|
@ -57,17 +54,20 @@ internal class StakingFeeTransactionLoader @AssistedInject constructor(
|
|||
) {
|
||||
val state = stateController.value
|
||||
val confirmationState = state.confirmationState as? StakingStates.ConfirmationState.Data
|
||||
?: error("No confirmation state")
|
||||
val validatorState = confirmationState.validatorState as? ValidatorState.Content
|
||||
?: error("Illegal state")
|
||||
val validatorState = state.validatorState as? StakingStates.ValidatorState.Data
|
||||
?: error("No validator provided")
|
||||
|
||||
val amount = (state.amountState as? AmountState.Data)?.amountTextField?.cryptoAmount?.value
|
||||
?: error("No amount provided")
|
||||
|
||||
val pendingAction = confirmationState.pendingAction
|
||||
val pendingActions = confirmationState.pendingActions
|
||||
|
||||
val validatorAddress = validatorState.chosenValidator.address
|
||||
|
||||
val approval = stakingApproval as? StakingApproval.Needed
|
||||
if (approval != null && state.actionType == StakingActionCommonType.ENTER) {
|
||||
if (approval != null && state.actionType == StakingActionCommonType.Enter) {
|
||||
val allowance = getAllowanceUseCase(
|
||||
userWalletId = userWallet.walletId,
|
||||
cryptoCurrency = cryptoCurrencyStatus.currency,
|
||||
|
|
@ -114,7 +114,7 @@ internal class StakingFeeTransactionLoader @AssistedInject constructor(
|
|||
val sourceAddress = cryptoCurrencyStatus.value.networkAddress?.defaultAddress?.value
|
||||
?: error("No available address")
|
||||
|
||||
val gasEstimate = if (isSolanaWithdraw(cryptoCurrencyStatus.currency.network.id.value, pendingActions)) {
|
||||
val gasEstimate = if (isComposePendingActions(cryptoCurrencyStatus.currency.network.id.value, pendingActions)) {
|
||||
val result = coroutineScope {
|
||||
pendingActions?.map { action ->
|
||||
async {
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ import com.tangem.blockchain.common.TransactionData
|
|||
import com.tangem.blockchain.common.transaction.Fee
|
||||
import com.tangem.common.ui.amountScreen.models.AmountState
|
||||
import com.tangem.domain.staking.*
|
||||
import com.tangem.domain.staking.model.PendingTransaction
|
||||
import com.tangem.domain.staking.model.SubmitHashData
|
||||
import com.tangem.domain.staking.model.stakekit.*
|
||||
import com.tangem.domain.staking.model.stakekit.action.StakingActionCommonType
|
||||
|
|
@ -21,7 +20,7 @@ import com.tangem.domain.utils.convertToSdkAmount
|
|||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.features.staking.impl.presentation.state.*
|
||||
import com.tangem.features.staking.impl.presentation.state.utils.checkAndCalculateSubtractedAmount
|
||||
import com.tangem.features.staking.impl.presentation.state.utils.isSolanaWithdraw
|
||||
import com.tangem.features.staking.impl.presentation.state.utils.isComposePendingActions
|
||||
import com.tangem.utils.extensions.orZero
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
|
|
@ -31,7 +30,6 @@ import kotlinx.coroutines.awaitAll
|
|||
import kotlinx.coroutines.coroutineScope
|
||||
import timber.log.Timber
|
||||
import java.math.BigDecimal
|
||||
import java.util.UUID
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
internal class StakingTransactionSender @AssistedInject constructor(
|
||||
|
|
@ -43,7 +41,6 @@ internal class StakingTransactionSender @AssistedInject constructor(
|
|||
private val getExplorerTransactionUrlUseCase: GetExplorerTransactionUrlUseCase,
|
||||
private val submitHashUseCase: SubmitHashUseCase,
|
||||
private val saveUnsubmittedHashUseCase: SaveUnsubmittedHashUseCase,
|
||||
private val savePendingTransactionUseCase: SavePendingTransactionUseCase,
|
||||
@Assisted private val cryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||
@Assisted private val userWallet: UserWallet,
|
||||
@Assisted private val yield: Yield,
|
||||
|
|
@ -51,7 +48,7 @@ internal class StakingTransactionSender @AssistedInject constructor(
|
|||
) {
|
||||
|
||||
private val balanceUpdater: StakingBalanceUpdater
|
||||
get() = stakingBalanceUpdater.create(cryptoCurrencyStatus, userWallet)
|
||||
get() = stakingBalanceUpdater.create(cryptoCurrencyStatus, userWallet, yield)
|
||||
|
||||
suspend fun constructAndSendTransactions(
|
||||
onConstructSuccess: (List<StakingTransaction>) -> Unit,
|
||||
|
|
@ -89,26 +86,21 @@ internal class StakingTransactionSender @AssistedInject constructor(
|
|||
|
||||
sendStakingTransaction(
|
||||
fullTransactionsData = fullTransactionsData,
|
||||
possiblePendingTransaction = composePendingTransaction(confirmationState),
|
||||
onSendSuccess = onSendSuccess,
|
||||
onSendError = onSendError,
|
||||
)
|
||||
}
|
||||
|
||||
private fun composePendingTransaction(confirmationState: StakingStates.ConfirmationState.Data): PendingTransaction {
|
||||
return confirmationState.possiblePendingTransaction ?: composeStakeTransaction(confirmationState)
|
||||
}
|
||||
|
||||
private suspend fun getStakingTransactions(
|
||||
state: StakingUiState,
|
||||
confirmationState: StakingStates.ConfirmationState.Data,
|
||||
onConstructError: (StakingError) -> Unit,
|
||||
) = coroutineScope {
|
||||
val isAllWithdrawAction = isSolanaWithdraw(
|
||||
val isComposePendingActions = isComposePendingActions(
|
||||
cryptoCurrencyStatus.currency.network.id.value,
|
||||
confirmationState.pendingActions,
|
||||
)
|
||||
if (isAllWithdrawAction) {
|
||||
if (isComposePendingActions) {
|
||||
confirmationState.pendingActions?.map { action ->
|
||||
async {
|
||||
getStakingTransaction(
|
||||
|
|
@ -169,7 +161,7 @@ internal class StakingTransactionSender @AssistedInject constructor(
|
|||
action: PendingAction? = confirmationState.pendingAction,
|
||||
onConstructError: (StakingError) -> Unit,
|
||||
): List<StakingTransaction> {
|
||||
val validatorState = confirmationState.validatorState as? ValidatorState.Content
|
||||
val validatorState = state.validatorState as? StakingStates.ValidatorState.Data
|
||||
?: error("No validator provided")
|
||||
val fee = (confirmationState.feeState as? FeeState.Content)?.fee
|
||||
?: error("No fee provided")
|
||||
|
|
@ -202,7 +194,6 @@ internal class StakingTransactionSender @AssistedInject constructor(
|
|||
|
||||
private suspend fun sendStakingTransaction(
|
||||
fullTransactionsData: List<FullTransactionData>,
|
||||
possiblePendingTransaction: PendingTransaction,
|
||||
onSendSuccess: (txUrl: String) -> Unit,
|
||||
onSendError: (SendTransactionError?) -> Unit,
|
||||
) {
|
||||
|
|
@ -218,24 +209,19 @@ internal class StakingTransactionSender @AssistedInject constructor(
|
|||
submitHash(
|
||||
transactions = fullTransactionsData.map { it.stakeKitTransaction },
|
||||
transactionHashes = transactionHashes,
|
||||
pendingTransaction = possiblePendingTransaction,
|
||||
)
|
||||
val txUrl = getExplorerTransactionUrlUseCase(
|
||||
txHash = transactionHashes.last(),
|
||||
networkId = cryptoCurrencyStatus.currency.network.id,
|
||||
).getOrElse { "" }
|
||||
|
||||
balanceUpdater.scheduleUpdates()
|
||||
balanceUpdater.fullUpdate()
|
||||
onSendSuccess(txUrl)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun submitHash(
|
||||
transactions: List<StakingTransaction>,
|
||||
transactionHashes: List<String>,
|
||||
pendingTransaction: PendingTransaction,
|
||||
) {
|
||||
private suspend fun submitHash(transactions: List<StakingTransaction>, transactionHashes: List<String>) {
|
||||
transactions
|
||||
.zip(transactionHashes)
|
||||
.forEach { (transaction, transactionHash) ->
|
||||
|
|
@ -243,7 +229,6 @@ internal class StakingTransactionSender @AssistedInject constructor(
|
|||
SubmitHashData(
|
||||
transactionId = transaction.id,
|
||||
transactionHash = transactionHash,
|
||||
pendingTransaction = pendingTransaction,
|
||||
),
|
||||
)
|
||||
.onLeft {
|
||||
|
|
@ -253,9 +238,6 @@ internal class StakingTransactionSender @AssistedInject constructor(
|
|||
)
|
||||
}.onRight {
|
||||
Timber.d("Successful hash submission")
|
||||
if (transaction.type != StakingTransactionType.FREEZE_ENERGY) {
|
||||
savePendingTransactionUseCase.invoke(userWallet.walletId, pendingTransaction)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -263,7 +245,7 @@ internal class StakingTransactionSender @AssistedInject constructor(
|
|||
private fun getAmount(amountState: AmountState.Data, fee: Fee, reduceAmountBy: BigDecimal?): BigDecimal {
|
||||
val amountValue = amountState.amountTextField.cryptoAmount.value ?: error("No amount value")
|
||||
val feeValue = fee.amount.value ?: error("No fee value")
|
||||
val isEnterAction = stateController.value.actionType == StakingActionCommonType.ENTER
|
||||
val isEnterAction = stateController.value.actionType == StakingActionCommonType.Enter
|
||||
|
||||
return checkAndCalculateSubtractedAmount(
|
||||
isAmountSubtractAvailable = isAmountSubtractAvailable && isEnterAction,
|
||||
|
|
@ -274,23 +256,6 @@ internal class StakingTransactionSender @AssistedInject constructor(
|
|||
)
|
||||
}
|
||||
|
||||
private fun composeStakeTransaction(confirmationState: StakingStates.ConfirmationState.Data): PendingTransaction {
|
||||
val state = stateController.value
|
||||
|
||||
val yieldBalance = cryptoCurrencyStatus.value.yieldBalance as? YieldBalance.Data
|
||||
val token = yield.getCurrentToken(cryptoCurrencyStatus.currency.id.rawCurrencyId)
|
||||
|
||||
return PendingTransaction(
|
||||
groupId = UUID.randomUUID().toString(),
|
||||
token = token,
|
||||
type = BalanceType.STAKED,
|
||||
amount = (state.amountState as? AmountState.Data)?.amountTextField?.cryptoAmount?.value ?: BigDecimal.ZERO,
|
||||
rawCurrencyId = cryptoCurrencyStatus.currency.id.rawCurrencyId,
|
||||
validator = (confirmationState.validatorState as? ValidatorState.Content)?.chosenValidator,
|
||||
balancesId = yieldBalance?.getBalancesUniqueId() ?: 0,
|
||||
)
|
||||
}
|
||||
|
||||
private data class FullTransactionData(
|
||||
val stakeKitTransaction: StakingTransaction,
|
||||
val tangemTransaction: TransactionData.Compiled,
|
||||
|
|
|
|||
|
|
@ -7,8 +7,6 @@ import com.tangem.core.ui.extensions.resourceReference
|
|||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.core.ui.extensions.wrappedList
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.staking.model.stakekit.Yield
|
||||
import com.tangem.domain.staking.model.stakekit.Yield.Validator.ValidatorStatus
|
||||
import com.tangem.features.staking.impl.R
|
||||
import com.tangem.features.staking.impl.presentation.state.*
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
|
|
@ -16,48 +14,6 @@ import java.math.BigDecimal
|
|||
|
||||
internal object ConfirmationStatePreviewData {
|
||||
|
||||
private val validatorList = listOf(
|
||||
Yield.Validator(
|
||||
address = "0xa6e768fef2d1af36c0cfdb276422e7881a83e951",
|
||||
status = ValidatorStatus.ACTIVE,
|
||||
name = "Luganodes",
|
||||
image = "https://assets.stakek.it/validators/luganodes.png",
|
||||
apr = BigDecimal("0.054823398040640445"),
|
||||
commission = 0.1,
|
||||
stakedBalance = "355544384.45009977",
|
||||
website = "https://luganodes.com/",
|
||||
votingPower = 0.09778360195377911,
|
||||
preferred = true,
|
||||
isStrategicPartner = false,
|
||||
),
|
||||
Yield.Validator(
|
||||
address = "0x35b1ca0f398905cf752e6fe122b51c88022fca32",
|
||||
status = ValidatorStatus.ACTIVE,
|
||||
name = "InfStones",
|
||||
image = "https://assets.stakek.it/validators/infstones.png",
|
||||
apr = BigDecimal("0.057786472172836965"),
|
||||
commission = 0.05,
|
||||
stakedBalance = "12495684.05643019",
|
||||
website = "https://infstones.com/",
|
||||
votingPower = 0.0034366257754399774,
|
||||
preferred = true,
|
||||
isStrategicPartner = false,
|
||||
),
|
||||
Yield.Validator(
|
||||
address = "0xd14a87025109013b0a2354a775cb335f926af65a",
|
||||
status = ValidatorStatus.ACTIVE,
|
||||
name = "Kiln",
|
||||
image = "https://assets.stakek.it/validators/kiln.png",
|
||||
apr = BigDecimal("0.057786472172836965"),
|
||||
commission = 0.05,
|
||||
stakedBalance = "85400369.96393165",
|
||||
website = "https://infstones.com/",
|
||||
votingPower = 0.023487238579718264,
|
||||
preferred = true,
|
||||
isStrategicPartner = true,
|
||||
),
|
||||
)
|
||||
|
||||
private val fee = Fee.Common(
|
||||
amount = Amount(
|
||||
currencySymbol = "MATIC",
|
||||
|
|
@ -77,11 +33,6 @@ internal object ConfirmationStatePreviewData {
|
|||
isFeeApproximate = false,
|
||||
isFeeConvertibleToFiat = true,
|
||||
),
|
||||
validatorState = ValidatorState.Content(
|
||||
isClickable = true,
|
||||
chosenValidator = validatorList[0],
|
||||
availableValidators = validatorList,
|
||||
),
|
||||
footerText = stringReference("You stake \$715.11 and will be receiving ~\$35 monthly"),
|
||||
notifications = persistentListOf(
|
||||
StakingNotification.Info.EarnRewards(
|
||||
|
|
@ -96,6 +47,5 @@ internal object ConfirmationStatePreviewData {
|
|||
isApprovalNeeded = false,
|
||||
reduceAmountBy = null,
|
||||
pendingActions = null,
|
||||
possiblePendingTransaction = null,
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
package com.tangem.features.staking.impl.presentation.state.previewdata
|
||||
|
||||
import com.tangem.domain.staking.model.stakekit.Yield
|
||||
import com.tangem.domain.staking.model.stakekit.Yield.Validator.ValidatorStatus
|
||||
import com.tangem.features.staking.impl.presentation.state.StakingStates
|
||||
import java.math.BigDecimal
|
||||
|
||||
internal object ValidatorStatePreviewData {
|
||||
|
||||
private val validatorList = listOf(
|
||||
Yield.Validator(
|
||||
address = "0xa6e768fef2d1af36c0cfdb276422e7881a83e951",
|
||||
status = ValidatorStatus.ACTIVE,
|
||||
name = "Luganodes",
|
||||
image = "https://assets.stakek.it/validators/luganodes.png",
|
||||
apr = BigDecimal("0.054823398040640445"),
|
||||
commission = 0.1,
|
||||
stakedBalance = "355544384.45009977",
|
||||
website = "https://luganodes.com/",
|
||||
votingPower = 0.09778360195377911,
|
||||
preferred = true,
|
||||
isStrategicPartner = false,
|
||||
),
|
||||
Yield.Validator(
|
||||
address = "0x35b1ca0f398905cf752e6fe122b51c88022fca32",
|
||||
status = ValidatorStatus.ACTIVE,
|
||||
name = "InfStones",
|
||||
image = "https://assets.stakek.it/validators/infstones.png",
|
||||
apr = BigDecimal("0.057786472172836965"),
|
||||
commission = 0.05,
|
||||
stakedBalance = "12495684.05643019",
|
||||
website = "https://infstones.com/",
|
||||
votingPower = 0.0034366257754399774,
|
||||
preferred = true,
|
||||
isStrategicPartner = true,
|
||||
),
|
||||
Yield.Validator(
|
||||
address = "0xd14a87025109013b0a2354a775cb335f926af65a",
|
||||
status = ValidatorStatus.ACTIVE,
|
||||
name = "Kiln",
|
||||
image = "https://assets.stakek.it/validators/kiln.png",
|
||||
apr = BigDecimal("0.057786472172836965"),
|
||||
commission = 0.05,
|
||||
stakedBalance = "85400369.96393165",
|
||||
website = "https://infstones.com/",
|
||||
votingPower = 0.023487238579718264,
|
||||
preferred = true,
|
||||
isStrategicPartner = false,
|
||||
),
|
||||
)
|
||||
|
||||
val validatorState = StakingStates.ValidatorState.Data(
|
||||
availableValidators = validatorList,
|
||||
chosenValidator = validatorList.first(),
|
||||
isPrimaryButtonEnabled = true,
|
||||
activeValidator = null,
|
||||
isClickable = true,
|
||||
isVisibleOnConfirmation = true,
|
||||
)
|
||||
}
|
||||
|
|
@ -2,14 +2,11 @@ package com.tangem.features.staking.impl.presentation.state.stub
|
|||
|
||||
import com.tangem.common.ui.bottomsheet.permission.state.ApproveType
|
||||
import com.tangem.common.ui.notifications.NotificationUM
|
||||
import com.tangem.domain.staking.model.stakekit.PendingAction
|
||||
import com.tangem.domain.staking.model.stakekit.Yield
|
||||
import com.tangem.domain.staking.model.stakekit.action.StakingActionCommonType
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.features.staking.impl.presentation.state.BalanceState
|
||||
import com.tangem.features.staking.impl.presentation.state.bottomsheet.InfoType
|
||||
import com.tangem.features.staking.impl.presentation.viewmodel.StakingClickIntents
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import java.math.BigDecimal
|
||||
|
||||
@Suppress("TooManyFunctions")
|
||||
|
|
@ -17,13 +14,7 @@ internal object StakingClickIntentsStub : StakingClickIntents {
|
|||
|
||||
override fun onBackClick() {}
|
||||
|
||||
override fun onNextClick(
|
||||
actionTypeToOverwrite: StakingActionCommonType?,
|
||||
pendingAction: PendingAction?,
|
||||
pendingActions: ImmutableList<PendingAction>?,
|
||||
balanceState: BalanceState?,
|
||||
) {
|
||||
}
|
||||
override fun onNextClick(balanceState: BalanceState?) {}
|
||||
|
||||
override fun onActionClick() {}
|
||||
|
||||
|
|
@ -67,8 +58,7 @@ internal object StakingClickIntentsStub : StakingClickIntents {
|
|||
|
||||
override fun onActiveStake(activeStake: BalanceState) {}
|
||||
|
||||
override fun getFee(pendingAction: PendingAction?, pendingActions: ImmutableList<PendingAction>?) {
|
||||
}
|
||||
override fun getFee() {}
|
||||
|
||||
override fun onAmountReduceByClick(
|
||||
reduceAmountBy: BigDecimal,
|
||||
|
|
|
|||
|
|
@ -1,33 +0,0 @@
|
|||
package com.tangem.features.staking.impl.presentation.state.transformers
|
||||
|
||||
import com.tangem.domain.staking.model.stakekit.BalanceType
|
||||
import com.tangem.domain.staking.model.stakekit.action.StakingActionCommonType
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
import com.tangem.domain.tokens.model.Network
|
||||
import com.tangem.features.staking.impl.presentation.state.*
|
||||
import com.tangem.lib.crypto.BlockchainUtils.isTron
|
||||
import com.tangem.utils.transformer.Transformer
|
||||
|
||||
internal class ActionTypeActiveStakeTransformer(
|
||||
private val cryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||
private val activeStake: BalanceState,
|
||||
) : Transformer<StakingUiState> {
|
||||
|
||||
override fun transform(prevState: StakingUiState): StakingUiState {
|
||||
val isTronStakedBalance = isTronStakedBalance(
|
||||
networkId = cryptoCurrencyStatus.currency.network.id,
|
||||
activeStake = activeStake,
|
||||
)
|
||||
val actionType = if (activeStake.pendingActions.isEmpty() || isTronStakedBalance) {
|
||||
StakingActionCommonType.EXIT
|
||||
} else {
|
||||
StakingActionCommonType.PENDING_OTHER
|
||||
}
|
||||
|
||||
return prevState.copy(actionType = actionType)
|
||||
}
|
||||
|
||||
private fun isTronStakedBalance(networkId: Network.ID, activeStake: BalanceState): Boolean {
|
||||
return isTron(networkId.value) && activeStake.type == BalanceType.STAKED
|
||||
}
|
||||
}
|
||||
|
|
@ -65,7 +65,7 @@ internal class AddStakingNotificationsTransformer(
|
|||
val feeValue = feeState?.fee?.amount?.value.orZero()
|
||||
val reduceAmountBy = confirmationState.reduceAmountBy.orZero()
|
||||
|
||||
val isEnterAction = prevState.actionType == StakingActionCommonType.ENTER
|
||||
val isEnterAction = prevState.actionType == StakingActionCommonType.Enter
|
||||
val isFeeCoverage = checkFeeCoverage(
|
||||
amountValue = amountValue,
|
||||
feeValue = feeValue,
|
||||
|
|
@ -93,12 +93,7 @@ internal class AddStakingNotificationsTransformer(
|
|||
prevState = prevState,
|
||||
feeError = feeError,
|
||||
sendingAmount = sendingAmount,
|
||||
onReload = {
|
||||
prevState.clickIntents.getFee(
|
||||
confirmationState.pendingAction,
|
||||
confirmationState.pendingActions,
|
||||
)
|
||||
},
|
||||
onReload = prevState.clickIntents::getFee,
|
||||
feeValue = feeValue,
|
||||
)
|
||||
// warnings
|
||||
|
|
@ -228,7 +223,7 @@ internal class AddStakingNotificationsTransformer(
|
|||
|
||||
val showNotification = sendingAmount + feeAmount > balance
|
||||
if (showNotification) {
|
||||
val notification = if (actionType == StakingActionCommonType.ENTER) {
|
||||
val notification = if (actionType == StakingActionCommonType.Enter) {
|
||||
NotificationUM.Error.TotalExceedsBalance
|
||||
} else {
|
||||
with(cryptoCurrencyStatus.currency) {
|
||||
|
|
@ -249,9 +244,9 @@ internal class AddStakingNotificationsTransformer(
|
|||
|
||||
private fun MutableList<NotificationUM>.addInfoNotifications(prevState: StakingUiState) {
|
||||
when (prevState.actionType) {
|
||||
StakingActionCommonType.EXIT -> addExitInfoNotifications()
|
||||
StakingActionCommonType.ENTER -> addEnterInfoNotifications()
|
||||
else -> addPendingInfoNotifications(prevState)
|
||||
StakingActionCommonType.Enter -> addEnterInfoNotifications()
|
||||
StakingActionCommonType.Exit -> addExitInfoNotifications()
|
||||
is StakingActionCommonType.Pending -> addPendingInfoNotifications(prevState)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -312,6 +307,10 @@ internal class AddStakingNotificationsTransformer(
|
|||
resourceReference(R.string.staking_revote) to
|
||||
resourceReference(R.string.staking_notifications_revote_tron_text)
|
||||
}
|
||||
StakingActionType.RESTAKE -> {
|
||||
resourceReference(R.string.staking_restake) to
|
||||
resourceReference(R.string.staking_notification_restake_text)
|
||||
}
|
||||
else -> null to null
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,25 +0,0 @@
|
|||
package com.tangem.features.staking.impl.presentation.state.transformers
|
||||
|
||||
import com.tangem.domain.staking.model.stakekit.PendingAction
|
||||
import com.tangem.domain.staking.model.stakekit.action.StakingActionCommonType
|
||||
import com.tangem.features.staking.impl.presentation.state.StakingStates
|
||||
import com.tangem.features.staking.impl.presentation.state.StakingUiState
|
||||
import com.tangem.utils.transformer.Transformer
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
||||
internal class SetActionToExecuteTransformer(
|
||||
private val actionTypeToOverwrite: StakingActionCommonType,
|
||||
private val pendingAction: PendingAction?,
|
||||
private val pendingActions: ImmutableList<PendingAction>?,
|
||||
) : Transformer<StakingUiState> {
|
||||
override fun transform(prevState: StakingUiState): StakingUiState {
|
||||
val confirmationState = prevState.confirmationState as? StakingStates.ConfirmationState.Data
|
||||
return prevState.copy(
|
||||
actionType = actionTypeToOverwrite,
|
||||
confirmationState = confirmationState?.copy(
|
||||
pendingAction = pendingAction,
|
||||
pendingActions = pendingActions,
|
||||
) ?: StakingStates.ConfirmationState.Empty(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -109,6 +109,7 @@ internal class SetButtonsStateTransformer(
|
|||
StakingStep.Confirmation -> getConfirmationButtonText()
|
||||
StakingStep.Validators -> resourceReference(R.string.common_continue)
|
||||
StakingStep.Amount,
|
||||
StakingStep.RestakeValidator,
|
||||
StakingStep.RewardsValidators,
|
||||
-> resourceReference(R.string.common_next)
|
||||
}
|
||||
|
|
@ -120,17 +121,15 @@ internal class SetButtonsStateTransformer(
|
|||
resourceReference(R.string.common_close)
|
||||
} else {
|
||||
when (actionType) {
|
||||
StakingActionCommonType.ENTER -> {
|
||||
StakingActionCommonType.Enter -> {
|
||||
if (confirmationState.isApprovalNeeded) {
|
||||
resourceReference(R.string.give_permission_title)
|
||||
} else {
|
||||
resourceReference(R.string.common_stake)
|
||||
}
|
||||
}
|
||||
StakingActionCommonType.EXIT -> resourceReference(R.string.common_unstake)
|
||||
StakingActionCommonType.PENDING_OTHER,
|
||||
StakingActionCommonType.PENDING_REWARDS,
|
||||
-> confirmationState.pendingAction?.type.getPendingActionTitle()
|
||||
StakingActionCommonType.Exit -> resourceReference(R.string.common_unstake)
|
||||
is StakingActionCommonType.Pending -> confirmationState.pendingAction?.type.getPendingActionTitle()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
|
@ -140,18 +139,11 @@ internal class SetButtonsStateTransformer(
|
|||
|
||||
private fun StakingUiState.onPrimaryClick() {
|
||||
when (currentStep) {
|
||||
StakingStep.InitialInfo -> clickIntents.onEnterClick()
|
||||
StakingStep.Validators -> {
|
||||
if (confirmationState is StakingStates.ConfirmationState.Data) {
|
||||
clickIntents.onNextClick(
|
||||
pendingActions = confirmationState.pendingActions,
|
||||
pendingAction = confirmationState.pendingAction,
|
||||
)
|
||||
} else {
|
||||
clickIntents.onNextClick()
|
||||
}
|
||||
}
|
||||
StakingStep.Amount -> clickIntents.onNextClick()
|
||||
StakingStep.InitialInfo -> clickIntents.onNextClick()
|
||||
StakingStep.Validators,
|
||||
StakingStep.RestakeValidator,
|
||||
-> clickIntents.onNextClick()
|
||||
StakingStep.Amount -> clickIntents.onEnterClick()
|
||||
StakingStep.Confirmation -> onConfirmationClick()
|
||||
StakingStep.RewardsValidators -> Unit
|
||||
}
|
||||
|
|
@ -162,7 +154,7 @@ internal class SetButtonsStateTransformer(
|
|||
if (confirmationState.innerState == InnerConfirmationStakingState.COMPLETED) {
|
||||
clickIntents.onNextClick()
|
||||
} else {
|
||||
val isEnterAction = actionType == StakingActionCommonType.ENTER
|
||||
val isEnterAction = actionType == StakingActionCommonType.Enter
|
||||
val isApproveNeeded = confirmationState.isApprovalNeeded
|
||||
|
||||
if (isEnterAction && isApproveNeeded) {
|
||||
|
|
@ -179,6 +171,7 @@ internal class SetButtonsStateTransformer(
|
|||
private fun StakingStep.isPrevButtonVisible(): Boolean = when (this) {
|
||||
StakingStep.InitialInfo,
|
||||
StakingStep.RewardsValidators,
|
||||
StakingStep.RestakeValidator,
|
||||
StakingStep.Confirmation,
|
||||
StakingStep.Validators,
|
||||
-> false
|
||||
|
|
@ -192,7 +185,9 @@ internal class SetButtonsStateTransformer(
|
|||
StakingStep.Amount -> amountState.isPrimaryButtonEnabled
|
||||
StakingStep.Confirmation -> confirmationState.isPrimaryButtonEnabled
|
||||
StakingStep.RewardsValidators -> rewardsValidatorsState.isPrimaryButtonEnabled
|
||||
StakingStep.Validators -> true
|
||||
StakingStep.RestakeValidator,
|
||||
StakingStep.Validators,
|
||||
-> true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2,22 +2,17 @@ package com.tangem.features.staking.impl.presentation.state.transformers
|
|||
|
||||
import com.tangem.blockchain.common.transaction.Fee
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.staking.model.stakekit.PendingAction
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
import com.tangem.features.staking.impl.presentation.state.FeeState
|
||||
import com.tangem.features.staking.impl.presentation.state.InnerConfirmationStakingState
|
||||
import com.tangem.features.staking.impl.presentation.state.StakingStates
|
||||
import com.tangem.features.staking.impl.presentation.state.StakingUiState
|
||||
import com.tangem.utils.Provider
|
||||
import com.tangem.utils.transformer.Transformer
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
||||
internal class SetConfirmationStateAssentTransformer(
|
||||
private val appCurrencyProvider: Provider<AppCurrency>,
|
||||
private val feeCryptoCurrencyStatus: CryptoCurrencyStatus?,
|
||||
private val fee: Fee,
|
||||
private val action: PendingAction?,
|
||||
private val actions: ImmutableList<PendingAction>?,
|
||||
) : Transformer<StakingUiState> {
|
||||
|
||||
override fun transform(prevState: StakingUiState): StakingUiState {
|
||||
|
|
@ -30,7 +25,6 @@ internal class SetConfirmationStateAssentTransformer(
|
|||
if (this is StakingStates.ConfirmationState.Data) {
|
||||
val isFeeConvertibleToFiat = feeCryptoCurrencyStatus?.currency?.network?.hasFiatFeeRate == true
|
||||
return copy(
|
||||
innerState = InnerConfirmationStakingState.ASSENT,
|
||||
feeState = FeeState.Content(
|
||||
fee = fee,
|
||||
rate = feeCryptoCurrencyStatus?.value?.fiatRate,
|
||||
|
|
@ -38,11 +32,7 @@ internal class SetConfirmationStateAssentTransformer(
|
|||
appCurrency = appCurrencyProvider(),
|
||||
isFeeApproximate = false,
|
||||
),
|
||||
validatorState = validatorState.copySealed(isClickable = true),
|
||||
pendingAction = action,
|
||||
pendingActions = actions,
|
||||
isPrimaryButtonEnabled = true,
|
||||
isApprovalNeeded = false,
|
||||
)
|
||||
} else {
|
||||
return this
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue