Updated on 2026-08-14
This commit is contained in:
commit
47dc1c38df
762 changed files with 23960 additions and 10924 deletions
|
|
@ -0,0 +1,43 @@
|
|||
package com.tangem.feature.wallet.child.organizetokens
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.tangem.core.decompose.context.AppComponentContext
|
||||
import com.tangem.core.decompose.model.getOrCreateModel
|
||||
import com.tangem.core.ui.decompose.ComposableBottomSheetComponent
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.feature.wallet.child.organizetokens.model.OrganizeTokensModel
|
||||
import com.tangem.feature.wallet.child.organizetokens.ui.OrganizeTokensContent
|
||||
|
||||
internal class OrganizeTokensComponent(
|
||||
appComponentContext: AppComponentContext,
|
||||
private val params: Params,
|
||||
) : ComposableBottomSheetComponent, AppComponentContext by appComponentContext {
|
||||
|
||||
private val model: OrganizeTokensModel = getOrCreateModel(params)
|
||||
|
||||
override fun dismiss() {
|
||||
params.callback.onDismiss()
|
||||
}
|
||||
|
||||
@Composable
|
||||
override fun BottomSheet() {
|
||||
val uiState by model.uiState.collectAsStateWithLifecycle()
|
||||
|
||||
OrganizeTokensContent(
|
||||
organizeTokensUM = uiState,
|
||||
dragAndDropIntents = model.dragAndDropAdapter,
|
||||
onDismiss = ::dismiss,
|
||||
)
|
||||
}
|
||||
|
||||
interface Callback {
|
||||
fun onDismiss()
|
||||
}
|
||||
|
||||
data class Params(
|
||||
val userWalletId: UserWalletId,
|
||||
val callback: Callback,
|
||||
)
|
||||
}
|
||||
|
|
@ -2,6 +2,7 @@ package com.tangem.feature.wallet.child.organizetokens.di
|
|||
|
||||
import com.tangem.core.decompose.di.ModelComponent
|
||||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.feature.wallet.child.organizetokens.model.OrganizeTokensModel
|
||||
import com.tangem.feature.wallet.child.organizetokens.model.OrganizeTokensModelLegacy
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
|
|
@ -17,4 +18,9 @@ internal interface OrganizeTokensModule {
|
|||
@IntoMap
|
||||
@ClassKey(OrganizeTokensModelLegacy::class)
|
||||
fun bindOrganizeTokensModelLegacy(model: OrganizeTokensModelLegacy): Model
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@ClassKey(OrganizeTokensModel::class)
|
||||
fun bindOrganizeTokensModel(model: OrganizeTokensModel): Model
|
||||
}
|
||||
|
|
@ -0,0 +1,116 @@
|
|||
package com.tangem.feature.wallet.child.organizetokens.entity
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import com.tangem.core.ui.ds.row.header.TangemHeaderRowUM
|
||||
import com.tangem.core.ui.ds.row.token.TangemTokenRowUM
|
||||
|
||||
/**
|
||||
* Helper class for the DND list items
|
||||
*
|
||||
* @property id ID of the item
|
||||
* @property roundingModeUM item [RoundingModeUM]
|
||||
* @property isShowShadow if true then item should be elevated
|
||||
* */
|
||||
@Immutable
|
||||
internal sealed class OrganizeRowItemUM {
|
||||
abstract val id: String
|
||||
abstract val roundingModeUM: RoundingModeUM
|
||||
abstract val isShowShadow: Boolean
|
||||
|
||||
/**
|
||||
* Item for token.
|
||||
*
|
||||
* @property id ID of the token
|
||||
* @property groupId ID of the network group which contains this token
|
||||
* @property accountId ID of account which contains this token
|
||||
* @property tokenRowUM state of the token item
|
||||
* @property roundingModeUM item [RoundingModeUM]
|
||||
* @property isShowShadow if true then item should be elevated
|
||||
* */
|
||||
data class Token(
|
||||
override val isShowShadow: Boolean = false,
|
||||
override val roundingModeUM: RoundingModeUM = RoundingModeUM.None,
|
||||
val tokenRowUM: TangemTokenRowUM,
|
||||
val groupId: String,
|
||||
val accountId: String = "",
|
||||
) : OrganizeRowItemUM() {
|
||||
override val id: String = tokenRowUM.id
|
||||
}
|
||||
|
||||
/**
|
||||
* Item for network group header.
|
||||
*
|
||||
* @property id ID of the network group
|
||||
* @property accountId ID of account which contains this network group
|
||||
* @property headerRowUM state of the network group header item
|
||||
* @property roundingModeUM item [RoundingModeUM]
|
||||
* @property isShowShadow if true then item should be elevated
|
||||
* */
|
||||
data class Network(
|
||||
override val roundingModeUM: RoundingModeUM = RoundingModeUM.None,
|
||||
override val isShowShadow: Boolean = false,
|
||||
val headerRowUM: TangemHeaderRowUM,
|
||||
val accountId: String = "",
|
||||
) : OrganizeRowItemUM() {
|
||||
override val id: String = headerRowUM.id
|
||||
}
|
||||
|
||||
/**
|
||||
* Item for portfolio.
|
||||
*
|
||||
* @property id ID of the portfolio
|
||||
* @property headerRowUM state of the portfolio item
|
||||
* @property roundingModeUM item [RoundingModeUM]
|
||||
* @property isShowShadow if true then item should be elevated
|
||||
* */
|
||||
data class Portfolio(
|
||||
override val roundingModeUM: RoundingModeUM = RoundingModeUM.None,
|
||||
val headerRowUM: TangemHeaderRowUM,
|
||||
) : OrganizeRowItemUM() {
|
||||
override val id: String = headerRowUM.id
|
||||
override val isShowShadow: Boolean = false
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper item used to detect possible positions where a draggable item can be placed.
|
||||
*
|
||||
* @property id ID of the placeholder for corresponding ID of the group
|
||||
* @property accountId ID of account which contains this placeholder
|
||||
* */
|
||||
data class Placeholder(
|
||||
override val id: String,
|
||||
val accountId: String = "",
|
||||
) : OrganizeRowItemUM() {
|
||||
override val isShowShadow: Boolean = false
|
||||
override val roundingModeUM: RoundingModeUM = RoundingModeUM.None
|
||||
}
|
||||
|
||||
/**
|
||||
* Update item [RoundingModeUM]
|
||||
*
|
||||
* @param mode new [RoundingModeUM]
|
||||
*
|
||||
* @return updated [DraggableItem]
|
||||
* */
|
||||
fun updateRoundingMode(mode: RoundingModeUM): OrganizeRowItemUM = when (this) {
|
||||
is Placeholder -> this
|
||||
is Portfolio -> this.copy(roundingModeUM = mode)
|
||||
is Network -> this.copy(roundingModeUM = mode)
|
||||
is Token -> this.copy(roundingModeUM = mode)
|
||||
}
|
||||
|
||||
/**
|
||||
* Update item shadow visibility
|
||||
*
|
||||
* @param show if true then item should be elevated
|
||||
*
|
||||
* @return updated [DraggableItem]
|
||||
* */
|
||||
fun updateShadowVisibility(show: Boolean): OrganizeRowItemUM = when (this) {
|
||||
is Portfolio,
|
||||
is Placeholder,
|
||||
-> this
|
||||
is Network -> this.copy(isShowShadow = show)
|
||||
is Token -> this.copy(isShowShadow = show)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
package com.tangem.feature.wallet.child.organizetokens.entity
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import com.tangem.core.ui.ds.button.TangemButtonUM
|
||||
import com.tangem.core.ui.event.StateEvent
|
||||
import kotlinx.collections.immutable.PersistentList
|
||||
|
||||
@Immutable
|
||||
internal data class OrganizeTokensUM(
|
||||
val tokenList: PersistentList<OrganizeRowItemUM>,
|
||||
val organizeMenuUM: OrganizeMenuUM,
|
||||
val isGrouped: Boolean,
|
||||
val isAccountsMode: Boolean,
|
||||
val scrollListToTop: StateEvent<Unit>,
|
||||
val cancelButton: TangemButtonUM,
|
||||
val applyButton: TangemButtonUM,
|
||||
val isBalanceHidden: Boolean,
|
||||
) {
|
||||
|
||||
data class OrganizeMenuUM(
|
||||
val isEnabled: Boolean = false,
|
||||
val isSortedByBalance: Boolean = false,
|
||||
val isGrouped: Boolean = false,
|
||||
val onSortClick: () -> Unit,
|
||||
val onGroupClick: () -> Unit,
|
||||
)
|
||||
}
|
||||
|
|
@ -5,6 +5,7 @@ import com.tangem.domain.account.status.model.AccountCryptoCurrencies
|
|||
import com.tangem.domain.models.account.filterCryptoPortfolio
|
||||
import com.tangem.domain.models.tokenlist.TokenList
|
||||
import com.tangem.feature.wallet.child.organizetokens.entity.DraggableItem
|
||||
import com.tangem.feature.wallet.child.organizetokens.entity.OrganizeRowItemUM
|
||||
import com.tangem.feature.wallet.child.organizetokens.entity.OrganizeTokensListUM
|
||||
|
||||
internal class CryptoCurrenciesIdsResolver {
|
||||
|
|
@ -35,4 +36,23 @@ internal class CryptoCurrenciesIdsResolver {
|
|||
.toList()
|
||||
}
|
||||
}
|
||||
|
||||
fun resolve(tokenList: List<OrganizeRowItemUM>, accountStatusList: AccountStatusList?): AccountCryptoCurrencies {
|
||||
if (accountStatusList == null) return emptyMap()
|
||||
|
||||
val draggableTokens = tokenList.filterIsInstance<OrganizeRowItemUM.Token>()
|
||||
|
||||
return accountStatusList.accountStatuses
|
||||
.filterCryptoPortfolio()
|
||||
.filter { it.tokenList != TokenList.Empty }
|
||||
.associate { accountStatus ->
|
||||
val currenciesById = accountStatus.flattenCurrencies().associateBy { it.currency.id.value }
|
||||
|
||||
accountStatus.account to draggableTokens
|
||||
.asSequence()
|
||||
.filter { it.accountId == accountStatus.account.accountId.value }
|
||||
.mapNotNull { token -> currenciesById[token.id]?.currency }
|
||||
.toList()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2,6 +2,7 @@ package com.tangem.feature.wallet.child.organizetokens.model
|
|||
|
||||
import androidx.compose.runtime.Stable
|
||||
import com.tangem.feature.wallet.child.organizetokens.entity.DraggableItem
|
||||
import com.tangem.feature.wallet.child.organizetokens.entity.OrganizeRowItemUM
|
||||
import org.burnoutcrew.reorderable.ItemPosition
|
||||
|
||||
internal interface OrganizeTokensIntents {
|
||||
|
|
@ -25,6 +26,7 @@ internal interface DragAndDropIntents {
|
|||
fun canDragItemOver(dragOver: ItemPosition, dragging: ItemPosition): Boolean
|
||||
|
||||
fun onItemDraggingStartLegacy(item: DraggableItem)
|
||||
fun onItemDraggingStart(item: OrganizeRowItemUM)
|
||||
|
||||
fun onItemDraggingEnd()
|
||||
}
|
||||
|
|
@ -0,0 +1,283 @@
|
|||
package com.tangem.feature.wallet.child.organizetokens.model
|
||||
|
||||
import androidx.compose.runtime.Stable
|
||||
import arrow.core.getOrElse
|
||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||
import com.tangem.core.analytics.models.AnalyticsParam
|
||||
import com.tangem.core.decompose.di.ModelScoped
|
||||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.core.decompose.model.ParamsContainer
|
||||
import com.tangem.core.ui.ds.button.TangemButtonType
|
||||
import com.tangem.core.ui.ds.button.TangemButtonUM
|
||||
import com.tangem.core.ui.event.consumedEvent
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.domain.account.models.AccountStatusList
|
||||
import com.tangem.domain.account.status.producer.SingleAccountStatusListProducer
|
||||
import com.tangem.domain.account.status.supplier.SingleAccountStatusListSupplier
|
||||
import com.tangem.domain.account.status.usecase.ApplyTokenListSortingUseCase
|
||||
import com.tangem.domain.account.status.usecase.ToggleTokenListGroupingUseCase
|
||||
import com.tangem.domain.account.status.usecase.ToggleTokenListSortingUseCase
|
||||
import com.tangem.domain.account.usecase.IsAccountsModeEnabledUseCase
|
||||
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase
|
||||
import com.tangem.domain.models.TokensSortType
|
||||
import com.tangem.feature.wallet.child.organizetokens.OrganizeTokensComponent
|
||||
import com.tangem.feature.wallet.child.organizetokens.analytics.PortfolioOrganizeTokensAnalyticsEvent
|
||||
import com.tangem.feature.wallet.child.organizetokens.entity.OrganizeTokensUM
|
||||
import com.tangem.feature.wallet.child.organizetokens.model.dnd.DragAndDropAdapter
|
||||
import com.tangem.feature.wallet.child.organizetokens.model.transformer.OrganizeContentStateTransformer
|
||||
import com.tangem.feature.wallet.child.organizetokens.model.transformer.OrganizeDisableBalanceSortingTransformer
|
||||
import com.tangem.feature.wallet.child.organizetokens.model.transformer.OrganizeSortingProgressStateTransformer
|
||||
import com.tangem.feature.wallet.impl.R
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import com.tangem.utils.transformer.update
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
@Stable
|
||||
@ModelScoped
|
||||
internal class OrganizeTokensModel @Inject constructor(
|
||||
paramsContainer: ParamsContainer,
|
||||
getBalanceHidingSettingsUseCase: GetBalanceHidingSettingsUseCase,
|
||||
override val dispatchers: CoroutineDispatcherProvider,
|
||||
private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
|
||||
private val analyticsEventsHandler: AnalyticsEventHandler,
|
||||
private val isAccountsModeEnabledUseCase: IsAccountsModeEnabledUseCase,
|
||||
private val singleAccountStatusListSupplier: SingleAccountStatusListSupplier,
|
||||
private val toggleTokenListGroupingUseCase: ToggleTokenListGroupingUseCase,
|
||||
private val toggleTokenListSortingUseCase: ToggleTokenListSortingUseCase,
|
||||
private val applyTokenListSortingUseCase: ApplyTokenListSortingUseCase,
|
||||
) : Model(), OrganizeTokensIntents {
|
||||
|
||||
private val params: OrganizeTokensComponent.Params = paramsContainer.require()
|
||||
|
||||
private val selectedAppCurrencyFlow = createSelectedAppCurrencyFlow()
|
||||
|
||||
private val userWalletId = params.userWalletId
|
||||
|
||||
private var cachedAccountStatusList: AccountStatusList? = null
|
||||
|
||||
private var isAccountsModeEnabled: Boolean = false
|
||||
|
||||
val uiState: StateFlow<OrganizeTokensUM>
|
||||
field = MutableStateFlow(getInitialState())
|
||||
|
||||
val dragAndDropAdapter by lazy(LazyThreadSafetyMode.NONE) {
|
||||
DragAndDropAdapter(uiState)
|
||||
}
|
||||
|
||||
init {
|
||||
analyticsEventsHandler.send(PortfolioOrganizeTokensAnalyticsEvent.ScreenOpened())
|
||||
|
||||
getBalanceHidingSettingsUseCase()
|
||||
.onEach { balanceSettings ->
|
||||
uiState.update {
|
||||
it.copy(
|
||||
isBalanceHidden = balanceSettings.isBalanceHidden,
|
||||
)
|
||||
}
|
||||
}
|
||||
.launchIn(modelScope)
|
||||
|
||||
bootstrapTokenList()
|
||||
bootstrapDragAndDropUpdates()
|
||||
}
|
||||
|
||||
override fun onBackClick() {
|
||||
params.callback.onDismiss()
|
||||
}
|
||||
|
||||
override fun onSortClick() {
|
||||
val list = cachedAccountStatusList ?: return
|
||||
if (list.sortType == TokensSortType.BALANCE) return
|
||||
|
||||
analyticsEventsHandler.send(PortfolioOrganizeTokensAnalyticsEvent.ByBalance())
|
||||
|
||||
modelScope.launch {
|
||||
toggleTokenListSortingUseCase(list).fold(
|
||||
ifLeft = {
|
||||
uiState.update {
|
||||
it.copy(
|
||||
organizeMenuUM = it.organizeMenuUM.copy(isEnabled = false),
|
||||
)
|
||||
}
|
||||
},
|
||||
ifRight = { accountStatusList ->
|
||||
uiState.update(
|
||||
OrganizeContentStateTransformer(
|
||||
accountStatusList = accountStatusList,
|
||||
isAccountsMode = isAccountsModeEnabled,
|
||||
appCurrency = selectedAppCurrencyFlow.value,
|
||||
),
|
||||
)
|
||||
cachedAccountStatusList = accountStatusList
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onGroupClick() {
|
||||
val list = cachedAccountStatusList ?: return
|
||||
|
||||
analyticsEventsHandler.send(PortfolioOrganizeTokensAnalyticsEvent.Group())
|
||||
|
||||
modelScope.launch {
|
||||
toggleTokenListGroupingUseCase(list).fold(
|
||||
ifLeft = {
|
||||
uiState.update {
|
||||
it.copy(
|
||||
organizeMenuUM = it.organizeMenuUM.copy(isEnabled = false),
|
||||
)
|
||||
}
|
||||
},
|
||||
ifRight = { accountStatusList ->
|
||||
uiState.update(
|
||||
OrganizeContentStateTransformer(
|
||||
accountStatusList = accountStatusList,
|
||||
isAccountsMode = isAccountsModeEnabled,
|
||||
appCurrency = selectedAppCurrencyFlow.value,
|
||||
),
|
||||
)
|
||||
cachedAccountStatusList = accountStatusList
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onApplyClick() {
|
||||
modelScope.launch {
|
||||
uiState.update(OrganizeSortingProgressStateTransformer(true))
|
||||
val resolver = CryptoCurrenciesIdsResolver()
|
||||
val isSortedByBalance = uiState.value.organizeMenuUM.isSortedByBalance
|
||||
val isGroupedByNetwork = uiState.value.isGrouped
|
||||
val tokensListUM = uiState.value.tokenList
|
||||
|
||||
sendAnalyticsEvent(
|
||||
isGroupedByNetwork = isGroupedByNetwork,
|
||||
isSortedByBalance = isSortedByBalance,
|
||||
)
|
||||
|
||||
val result = applyTokenListSortingUseCase(
|
||||
sortedTokensIdsByAccount = resolver.resolve(tokensListUM, cachedAccountStatusList),
|
||||
isGroupedByNetwork = isGroupedByNetwork,
|
||||
isSortedByBalance = isSortedByBalance,
|
||||
)
|
||||
|
||||
result.fold(
|
||||
ifLeft = {
|
||||
uiState.update {
|
||||
it.copy(
|
||||
organizeMenuUM = it.organizeMenuUM.copy(isEnabled = false),
|
||||
)
|
||||
}
|
||||
},
|
||||
ifRight = {
|
||||
onBackClick()
|
||||
uiState.update(OrganizeSortingProgressStateTransformer(false))
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCancelClick() {
|
||||
analyticsEventsHandler.send(PortfolioOrganizeTokensAnalyticsEvent.Cancel())
|
||||
onBackClick()
|
||||
}
|
||||
|
||||
private fun bootstrapTokenList() {
|
||||
modelScope.launch {
|
||||
val accountList = singleAccountStatusListSupplier.getSyncOrNull(
|
||||
SingleAccountStatusListProducer.Params(userWalletId),
|
||||
) ?: return@launch
|
||||
|
||||
isAccountsModeEnabled = isAccountsModeEnabledUseCase.invokeSync()
|
||||
|
||||
uiState.update(
|
||||
transformer = OrganizeContentStateTransformer(
|
||||
accountStatusList = accountList,
|
||||
isAccountsMode = isAccountsModeEnabled,
|
||||
appCurrency = selectedAppCurrencyFlow.value,
|
||||
),
|
||||
)
|
||||
|
||||
cachedAccountStatusList = accountList
|
||||
}
|
||||
}
|
||||
|
||||
private fun bootstrapDragAndDropUpdates() {
|
||||
dragAndDropAdapter.dragAndDropUpdates
|
||||
.filterNotNull()
|
||||
.distinctUntilChanged()
|
||||
.onEach { (type, updatedTokenList) ->
|
||||
disableSortingByBalanceIfListChanged(type)
|
||||
uiState.update { it.copy(tokenList = updatedTokenList) }
|
||||
}
|
||||
.launchIn(modelScope)
|
||||
}
|
||||
|
||||
private fun disableSortingByBalanceIfListChanged(dragOperationType: DragAndDropAdapter.DragOperation.Type) {
|
||||
if (dragOperationType !is DragAndDropAdapter.DragOperation.Type.End) return
|
||||
|
||||
if (uiState.value.organizeMenuUM.isSortedByBalance && dragOperationType.isItemsOrderChanged) {
|
||||
cachedAccountStatusList = cachedAccountStatusList?.copy(sortType = TokensSortType.NONE)
|
||||
uiState.update(OrganizeDisableBalanceSortingTransformer)
|
||||
}
|
||||
}
|
||||
|
||||
private fun createSelectedAppCurrencyFlow(): StateFlow<AppCurrency> {
|
||||
return getSelectedAppCurrencyUseCase()
|
||||
.map { maybeAppCurrency ->
|
||||
maybeAppCurrency.getOrElse { AppCurrency.Default }
|
||||
}
|
||||
.stateIn(
|
||||
scope = modelScope,
|
||||
started = SharingStarted.Eagerly,
|
||||
initialValue = AppCurrency.Default,
|
||||
)
|
||||
}
|
||||
|
||||
private fun sendAnalyticsEvent(isGroupedByNetwork: Boolean, isSortedByBalance: Boolean) {
|
||||
analyticsEventsHandler.send(
|
||||
PortfolioOrganizeTokensAnalyticsEvent.Apply(
|
||||
grouping = if (isGroupedByNetwork) {
|
||||
AnalyticsParam.OnOffState.On
|
||||
} else {
|
||||
AnalyticsParam.OnOffState.Off
|
||||
},
|
||||
organizeSortType = if (isSortedByBalance) {
|
||||
AnalyticsParam.OrganizeSortType.ByBalance
|
||||
} else {
|
||||
AnalyticsParam.OrganizeSortType.Manually
|
||||
},
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private fun getInitialState(): OrganizeTokensUM {
|
||||
return OrganizeTokensUM(
|
||||
tokenList = persistentListOf(),
|
||||
organizeMenuUM = OrganizeTokensUM.OrganizeMenuUM(
|
||||
onSortClick = ::onSortClick,
|
||||
onGroupClick = ::onGroupClick,
|
||||
),
|
||||
cancelButton = TangemButtonUM(
|
||||
text = resourceReference(R.string.common_cancel),
|
||||
onClick = ::onCancelClick,
|
||||
type = TangemButtonType.Secondary,
|
||||
),
|
||||
applyButton = TangemButtonUM(
|
||||
text = resourceReference(R.string.common_apply),
|
||||
onClick = ::onApplyClick,
|
||||
type = TangemButtonType.Primary,
|
||||
),
|
||||
scrollListToTop = consumedEvent(),
|
||||
isBalanceHidden = true,
|
||||
isGrouped = false,
|
||||
isAccountsMode = false,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,10 +1,18 @@
|
|||
package com.tangem.feature.wallet.child.organizetokens.model.common
|
||||
|
||||
import com.tangem.feature.wallet.child.organizetokens.entity.DraggableItem
|
||||
import com.tangem.feature.wallet.child.organizetokens.entity.OrganizeRowItemUM
|
||||
|
||||
internal fun getGroupPlaceholderLegacy(index: Int, accountId: String = ""): DraggableItem.Placeholder {
|
||||
return DraggableItem.Placeholder(
|
||||
id = "placeholder_${accountId}_${index.inc()}",
|
||||
accountId = accountId,
|
||||
)
|
||||
}
|
||||
|
||||
internal fun getGroupPlaceholder(index: Int, accountId: String = ""): OrganizeRowItemUM.Placeholder {
|
||||
return OrganizeRowItemUM.Placeholder(
|
||||
id = "placeholder_${accountId}_${index.inc()}",
|
||||
accountId = accountId,
|
||||
)
|
||||
}
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
package com.tangem.feature.wallet.child.organizetokens.model.common
|
||||
|
||||
import com.tangem.feature.wallet.child.organizetokens.entity.DraggableItem
|
||||
import com.tangem.feature.wallet.child.organizetokens.entity.OrganizeRowItemUM
|
||||
import com.tangem.feature.wallet.child.organizetokens.entity.RoundingModeUM
|
||||
|
||||
internal fun List<DraggableItem>.uniteItemsLegacy(isAccountsMode: Boolean): List<DraggableItem> {
|
||||
|
|
@ -45,6 +46,48 @@ internal fun List<DraggableItem>.uniteItemsLegacy(isAccountsMode: Boolean): List
|
|||
}.toList()
|
||||
}
|
||||
|
||||
internal fun List<OrganizeRowItemUM>.uniteItems(isAccountsMode: Boolean): List<OrganizeRowItemUM> {
|
||||
val items = this
|
||||
val lastItemIndex = items.lastIndex
|
||||
|
||||
return items
|
||||
.asSequence()
|
||||
.mapIndexed { index, item ->
|
||||
val mode = when (index) {
|
||||
0 -> if (item is OrganizeRowItemUM.Placeholder) {
|
||||
RoundingModeUM.None
|
||||
} else {
|
||||
RoundingModeUM.Top()
|
||||
}
|
||||
lastItemIndex -> RoundingModeUM.Bottom()
|
||||
1 -> if (items.first() is OrganizeRowItemUM.Placeholder) {
|
||||
RoundingModeUM.Top()
|
||||
} else {
|
||||
RoundingModeUM.None
|
||||
}
|
||||
else -> when (item) {
|
||||
is OrganizeRowItemUM.Placeholder -> RoundingModeUM.None
|
||||
is OrganizeRowItemUM.Network -> if (isAccountsMode) {
|
||||
RoundingModeUM.None
|
||||
} else {
|
||||
RoundingModeUM.Top(isShowGap = true)
|
||||
}
|
||||
is OrganizeRowItemUM.Token -> applyRoundingModeToToken(
|
||||
isAccountsMode = isAccountsMode,
|
||||
items = items,
|
||||
index = index,
|
||||
lastItemIndex = lastItemIndex,
|
||||
)
|
||||
is OrganizeRowItemUM.Portfolio -> RoundingModeUM.Top(isShowGap = true)
|
||||
}
|
||||
}
|
||||
|
||||
item
|
||||
.updateRoundingMode(mode)
|
||||
.updateShadowVisibility(show = false)
|
||||
}.toList()
|
||||
}
|
||||
|
||||
internal fun List<DraggableItem>.divideMovingItem(movingItem: DraggableItem): List<DraggableItem> {
|
||||
val mutableList = this.toMutableList()
|
||||
val listIterator = mutableList.listIterator()
|
||||
|
|
@ -110,4 +153,51 @@ private fun applyRoundingModeToTokenLegacy(
|
|||
RoundingModeUM.Bottom(isShowGap = true)
|
||||
}
|
||||
else -> RoundingModeUM.None
|
||||
}
|
||||
|
||||
/**
|
||||
* Applying rounding to tokens
|
||||
*
|
||||
* If is in accounts mode without grouping
|
||||
* * PORTFOLIO
|
||||
* * TOKEN
|
||||
* * TOKEN <- add rounding
|
||||
* * PORTFOLIO index + 1 is PORTFOLIO
|
||||
*
|
||||
* If is in accounts mode with grouping
|
||||
* * PORTFOLIO
|
||||
* * PLACEHOLDER
|
||||
* * GROUPING
|
||||
* * TOKEN
|
||||
* * TOKEN <- add rounding
|
||||
* * PLACEHOLDER index + 1 is PLACEHOLDER
|
||||
* * PORTFOLIO index + 2 is PORTFOLIO
|
||||
* * PLACEHOLDER
|
||||
*
|
||||
* If is not accounts mode without grouping
|
||||
* * TOKEN
|
||||
* * TOKEN <- add rounding
|
||||
*
|
||||
* If is not accounts mode with grouping
|
||||
* * PLACEHOLDER
|
||||
* * GROUPING
|
||||
* * TOKEN
|
||||
* * TOKEN <- add rounding
|
||||
* * PLACEHOLDER index + 1 is PLACEHOLDER
|
||||
*/
|
||||
private fun applyRoundingModeToToken(
|
||||
isAccountsMode: Boolean,
|
||||
items: List<OrganizeRowItemUM>,
|
||||
index: Int,
|
||||
lastItemIndex: Int,
|
||||
) = when {
|
||||
isAccountsMode && index + 1 < lastItemIndex &&
|
||||
(items[index + 1] is OrganizeRowItemUM.Portfolio ||
|
||||
items[index + 1] is OrganizeRowItemUM.Placeholder && items[index + 2] is OrganizeRowItemUM.Portfolio) -> {
|
||||
RoundingModeUM.Bottom(isShowGap = true)
|
||||
}
|
||||
(!isAccountsMode || index + 1 == lastItemIndex) && items[index + 1] is OrganizeRowItemUM.Placeholder -> {
|
||||
RoundingModeUM.Bottom(isShowGap = true)
|
||||
}
|
||||
else -> RoundingModeUM.None
|
||||
}
|
||||
|
|
@ -0,0 +1,82 @@
|
|||
package com.tangem.feature.wallet.child.organizetokens.model.converter
|
||||
|
||||
import com.tangem.domain.account.models.AccountStatusList
|
||||
import com.tangem.domain.account.status.model.AccountCryptoCurrencyStatus
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.models.account.AccountStatus
|
||||
import com.tangem.domain.models.account.filterCryptoPortfolio
|
||||
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
||||
import com.tangem.domain.models.tokenlist.TokenList
|
||||
import com.tangem.feature.wallet.child.organizetokens.entity.OrganizeRowItemUM
|
||||
import com.tangem.feature.wallet.child.organizetokens.model.common.getGroupPlaceholder
|
||||
import com.tangem.feature.wallet.child.organizetokens.model.common.uniteItems
|
||||
import com.tangem.feature.wallet.child.organizetokens.model.converter.items.OrganizeAccountItemConverter
|
||||
import com.tangem.feature.wallet.child.organizetokens.model.converter.items.OrganizeNetworkItemConverter
|
||||
import com.tangem.feature.wallet.child.organizetokens.model.converter.items.OrganizeTokenItemConverter
|
||||
import com.tangem.utils.converter.Converter
|
||||
import com.tangem.utils.extensions.addIf
|
||||
import kotlinx.collections.immutable.PersistentList
|
||||
import kotlinx.collections.immutable.toPersistentList
|
||||
|
||||
internal class OrganizeTokensListConverter(
|
||||
private val isAccountsMode: Boolean,
|
||||
private val appCurrency: AppCurrency,
|
||||
) : Converter<AccountStatusList, PersistentList<OrganizeRowItemUM>> {
|
||||
|
||||
private val accountItemConverter by lazy(LazyThreadSafetyMode.NONE) {
|
||||
OrganizeAccountItemConverter(appCurrency)
|
||||
}
|
||||
|
||||
private val tokenItemConverter by lazy(LazyThreadSafetyMode.NONE) {
|
||||
OrganizeTokenItemConverter(appCurrency)
|
||||
}
|
||||
|
||||
override fun convert(value: AccountStatusList): PersistentList<OrganizeRowItemUM> {
|
||||
return value.accountStatuses
|
||||
.asSequence()
|
||||
.filterCryptoPortfolio()
|
||||
.flatMap { accountStatus ->
|
||||
buildList {
|
||||
addIf(
|
||||
condition = isAccountsMode,
|
||||
create = { accountItemConverter.convert(accountStatus) },
|
||||
)
|
||||
when (val tokenList = accountStatus.tokenList) {
|
||||
is TokenList.Ungrouped -> addAll(
|
||||
elements = tokenItemConverter.convertList(
|
||||
input = tokenList.currencies.mapToAccountCryptoCurrencyStatus(accountStatus),
|
||||
),
|
||||
)
|
||||
is TokenList.GroupedByNetwork -> {
|
||||
addIf(
|
||||
condition = isAccountsMode,
|
||||
create = { getGroupPlaceholder(-1, accountStatus.accountId.value) },
|
||||
)
|
||||
tokenList.groups.asSequence().forEachIndexed { index, (groupNetwork, currencies) ->
|
||||
add(OrganizeNetworkItemConverter.convert(accountStatus.accountId to groupNetwork))
|
||||
addAll(
|
||||
elements = tokenItemConverter.convertList(
|
||||
input = currencies.mapToAccountCryptoCurrencyStatus(accountStatus),
|
||||
),
|
||||
)
|
||||
add(getGroupPlaceholder(index, accountStatus.accountId.value))
|
||||
}
|
||||
}
|
||||
TokenList.Empty -> Unit
|
||||
}
|
||||
}
|
||||
}.toList()
|
||||
.uniteItems(isAccountsMode).toPersistentList()
|
||||
}
|
||||
|
||||
private fun List<CryptoCurrencyStatus>.mapToAccountCryptoCurrencyStatus(
|
||||
accountStatus: AccountStatus.CryptoPortfolio,
|
||||
): List<AccountCryptoCurrencyStatus> {
|
||||
return map { cryptoCurrencyStatus ->
|
||||
AccountCryptoCurrencyStatus(
|
||||
account = accountStatus.account,
|
||||
status = cryptoCurrencyStatus,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
package com.tangem.feature.wallet.child.organizetokens.model.converter.items
|
||||
|
||||
import com.tangem.common.ui.account.toUM
|
||||
import com.tangem.core.ui.ds.row.header.TangemHeaderRowUM
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
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.models.TotalFiatBalance
|
||||
import com.tangem.domain.models.account.AccountStatus
|
||||
import com.tangem.feature.wallet.child.organizetokens.entity.OrganizeRowItemUM
|
||||
import com.tangem.utils.converter.Converter
|
||||
|
||||
internal class OrganizeAccountItemConverter(
|
||||
private val appCurrency: AppCurrency,
|
||||
) : Converter<AccountStatus.CryptoPortfolio, OrganizeRowItemUM.Portfolio> {
|
||||
|
||||
override fun convert(value: AccountStatus.CryptoPortfolio): OrganizeRowItemUM.Portfolio {
|
||||
val accountBalance = value.tokenList.totalFiatBalance as? TotalFiatBalance.Loaded
|
||||
return OrganizeRowItemUM.Portfolio(
|
||||
headerRowUM = TangemHeaderRowUM(
|
||||
id = value.accountId.value,
|
||||
title = value.account.accountName.toUM().value,
|
||||
subtitle = stringReference(
|
||||
accountBalance?.amount.format {
|
||||
fiat(
|
||||
fiatCurrencyCode = appCurrency.code,
|
||||
fiatCurrencySymbol = appCurrency.symbol,
|
||||
)
|
||||
},
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
package com.tangem.feature.wallet.child.organizetokens.model.converter.items
|
||||
|
||||
import com.tangem.core.ui.ds.row.header.TangemHeaderRowUM
|
||||
import com.tangem.core.ui.ds.row.internal.TangemRowTailUM
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.domain.models.account.AccountId
|
||||
import com.tangem.domain.models.network.Network
|
||||
import com.tangem.feature.wallet.child.organizetokens.entity.OrganizeRowItemUM
|
||||
import com.tangem.feature.wallet.impl.R
|
||||
import com.tangem.utils.converter.Converter
|
||||
|
||||
internal object OrganizeNetworkItemConverter : Converter<Pair<AccountId, Network>, OrganizeRowItemUM.Network> {
|
||||
|
||||
override fun convert(value: Pair<AccountId, Network>): OrganizeRowItemUM.Network {
|
||||
val (accountId, groupNetwork) = value
|
||||
return OrganizeRowItemUM.Network(
|
||||
headerRowUM = TangemHeaderRowUM(
|
||||
id = groupNetwork.id.toString(),
|
||||
title = stringReference(groupNetwork.name),
|
||||
tailUM = TangemRowTailUM.Draggable(R.drawable.ic_group_drop_24),
|
||||
),
|
||||
accountId = accountId.value,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,67 @@
|
|||
package com.tangem.feature.wallet.child.organizetokens.model.converter.items
|
||||
|
||||
import androidx.compose.ui.text.SpanStyle
|
||||
import com.tangem.common.getTotalCryptoAmount
|
||||
import com.tangem.common.getTotalFiatAmount
|
||||
import com.tangem.core.ui.components.currency.icon.converter.CryptoCurrencyToIconStateConverter
|
||||
import com.tangem.core.ui.ds.image.TangemIconUM
|
||||
import com.tangem.core.ui.ds.row.internal.TangemRowTailUM
|
||||
import com.tangem.core.ui.ds.row.token.TangemTokenRowUM
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.core.ui.format.bigdecimal.cryptoStyled
|
||||
import com.tangem.core.ui.format.bigdecimal.fiat
|
||||
import com.tangem.core.ui.format.bigdecimal.formatStyled
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.domain.account.status.model.AccountCryptoCurrencyStatus
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.feature.wallet.child.organizetokens.entity.OrganizeRowItemUM
|
||||
import com.tangem.feature.wallet.child.organizetokens.model.common.getTokenItemId
|
||||
import com.tangem.feature.wallet.impl.R
|
||||
import com.tangem.utils.converter.Converter
|
||||
|
||||
internal class OrganizeTokenItemConverter(
|
||||
private val appCurrency: AppCurrency,
|
||||
) : Converter<AccountCryptoCurrencyStatus, OrganizeRowItemUM.Token> {
|
||||
|
||||
private val iconStateConverter by lazy(LazyThreadSafetyMode.NONE) {
|
||||
CryptoCurrencyToIconStateConverter()
|
||||
}
|
||||
|
||||
override fun convert(value: AccountCryptoCurrencyStatus): OrganizeRowItemUM.Token {
|
||||
val (account, currencyStatus) = value
|
||||
val currency = currencyStatus.currency
|
||||
|
||||
return OrganizeRowItemUM.Token(
|
||||
tokenRowUM = TangemTokenRowUM.Actionable(
|
||||
id = getTokenItemId(currency.id),
|
||||
headIconUM = TangemIconUM.Currency(iconStateConverter.convert(currencyStatus)),
|
||||
titleUM = TangemTokenRowUM.TitleUM.Content(
|
||||
text = stringReference(currency.name),
|
||||
),
|
||||
subtitleUM = TangemTokenRowUM.SubtitleUM.Empty,
|
||||
topEndContentUM = TangemTokenRowUM.EndContentUM.Content(
|
||||
text = currencyStatus.getTotalFiatAmount().formatStyled {
|
||||
fiat(
|
||||
fiatCurrencyCode = appCurrency.code,
|
||||
fiatCurrencySymbol = appCurrency.symbol,
|
||||
spanStyleReference = { SpanStyle(color = TangemTheme.colors2.text.neutral.secondary) },
|
||||
)
|
||||
},
|
||||
),
|
||||
bottomEndContentUM = TangemTokenRowUM.EndContentUM.Content(
|
||||
text = currencyStatus.getTotalCryptoAmount().formatStyled {
|
||||
cryptoStyled(
|
||||
cryptoCurrency = currency,
|
||||
spanStyleReference = { SpanStyle(color = TangemTheme.colors2.text.neutral.secondary) },
|
||||
)
|
||||
},
|
||||
),
|
||||
tailUM = TangemRowTailUM.Draggable(R.drawable.ic_drag_24),
|
||||
onItemClick = null,
|
||||
onItemLongClick = null,
|
||||
),
|
||||
groupId = currency.network.id.toString(),
|
||||
accountId = account.accountId.value,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,217 @@
|
|||
package com.tangem.feature.wallet.child.organizetokens.model.dnd
|
||||
|
||||
import com.tangem.feature.wallet.child.organizetokens.entity.DraggableItem
|
||||
import com.tangem.feature.wallet.child.organizetokens.entity.OrganizeRowItemUM
|
||||
import com.tangem.feature.wallet.child.organizetokens.entity.OrganizeTokensUM
|
||||
import com.tangem.feature.wallet.child.organizetokens.entity.RoundingModeUM
|
||||
import com.tangem.feature.wallet.child.organizetokens.model.DragAndDropIntents
|
||||
import com.tangem.feature.wallet.child.organizetokens.model.common.uniteItems
|
||||
import kotlinx.collections.immutable.PersistentList
|
||||
import kotlinx.collections.immutable.mutate
|
||||
import kotlinx.collections.immutable.toPersistentList
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import org.burnoutcrew.reorderable.ItemPosition
|
||||
|
||||
internal class DragAndDropAdapter(
|
||||
private val organizeTokensUMFlow: StateFlow<OrganizeTokensUM>,
|
||||
) : DragAndDropIntents {
|
||||
|
||||
private var draggingItem: OrganizeRowItemUM? = null
|
||||
private var draggingListState: OrganizeTokensUM? = null
|
||||
|
||||
private val tokensUM: OrganizeTokensUM
|
||||
get() = organizeTokensUMFlow.value
|
||||
|
||||
private val draggableGroupsOperations by lazy(LazyThreadSafetyMode.NONE) {
|
||||
DraggableGroupsOperations()
|
||||
}
|
||||
|
||||
val dragAndDropUpdates: StateFlow<DragOperation?>
|
||||
field = MutableStateFlow(value = null)
|
||||
|
||||
override fun canDragItemOver(dragOver: ItemPosition, dragging: ItemPosition): Boolean {
|
||||
val tokensListUM = tokensUM.tokenList
|
||||
|
||||
val (dragOverItem, draggingItem) = findItemsToMove(
|
||||
items = tokensListUM,
|
||||
moveOverItemKey = dragOver.key,
|
||||
movedItemKey = dragging.key,
|
||||
)
|
||||
|
||||
if (dragOverItem == null || draggingItem == null) {
|
||||
return false
|
||||
}
|
||||
|
||||
val canDrag = when (draggingItem) {
|
||||
is OrganizeRowItemUM.Network -> checkCanMoveHeaderOver(
|
||||
item = draggingItem,
|
||||
moveOverItem = dragOverItem,
|
||||
)
|
||||
is OrganizeRowItemUM.Token -> checkCanMoveTokenOver(
|
||||
item = draggingItem,
|
||||
moveOverItem = dragOverItem,
|
||||
isAccountsMode = tokensUM.isAccountsMode,
|
||||
isGrouped = tokensUM.isGrouped,
|
||||
)
|
||||
is OrganizeRowItemUM.Placeholder,
|
||||
is OrganizeRowItemUM.Portfolio,
|
||||
-> false
|
||||
}
|
||||
|
||||
return canDrag
|
||||
}
|
||||
|
||||
override fun onItemDraggingStartLegacy(item: DraggableItem) {
|
||||
/* no-op */
|
||||
}
|
||||
|
||||
override fun onItemDraggingStart(item: OrganizeRowItemUM) {
|
||||
if (draggingItem != null) return
|
||||
draggingItem = item
|
||||
|
||||
dragAndDropUpdates.value = DragOperation(
|
||||
type = DragOperation.Type.Start,
|
||||
tokenList = when (item) {
|
||||
is OrganizeRowItemUM.Placeholder,
|
||||
is OrganizeRowItemUM.Portfolio,
|
||||
-> tokensUM.tokenList
|
||||
is OrganizeRowItemUM.Network -> draggableGroupsOperations
|
||||
.collapseGroup(tokensUM.tokenList, item)
|
||||
.divideMovingItem(item)
|
||||
is OrganizeRowItemUM.Token -> tokensUM.tokenList.divideMovingItem(item)
|
||||
}.toPersistentList(),
|
||||
)
|
||||
|
||||
draggingListState = tokensUM
|
||||
}
|
||||
|
||||
override fun onItemDraggingEnd() {
|
||||
val draggingItem = draggingItem ?: return
|
||||
|
||||
dragAndDropUpdates.value = DragOperation(
|
||||
type = DragOperation.Type.End(isItemsOrderChanged = checkIsItemsOrderChanged(tokensUM)),
|
||||
tokenList = when (draggingItem) {
|
||||
is OrganizeRowItemUM.Network -> draggableGroupsOperations
|
||||
.expandGroups(tokensUM.tokenList, tokensUM.isAccountsMode)
|
||||
.uniteItems(tokensUM.isAccountsMode)
|
||||
is OrganizeRowItemUM.Token -> tokensUM.tokenList.uniteItems(tokensUM.isAccountsMode)
|
||||
is OrganizeRowItemUM.Placeholder,
|
||||
is OrganizeRowItemUM.Portfolio,
|
||||
-> tokensUM.tokenList
|
||||
}.toPersistentList(),
|
||||
)
|
||||
|
||||
this.draggingItem = null
|
||||
}
|
||||
|
||||
override fun onItemDragged(from: ItemPosition, to: ItemPosition) {
|
||||
dragAndDropUpdates.value = DragOperation(
|
||||
type = DragOperation.Type.Dragged,
|
||||
tokenList = tokensUM.tokenList.mutate {
|
||||
it.add(to.index, it.removeAt(from.index))
|
||||
}.toPersistentList(),
|
||||
)
|
||||
}
|
||||
|
||||
private fun findItemsToMove(
|
||||
items: List<OrganizeRowItemUM>,
|
||||
moveOverItemKey: Any?,
|
||||
movedItemKey: Any?,
|
||||
): Pair<OrganizeRowItemUM?, OrganizeRowItemUM?> {
|
||||
var moveOverItem: OrganizeRowItemUM? = null
|
||||
var movedItem: OrganizeRowItemUM? = null
|
||||
|
||||
for (item in items) {
|
||||
if (item.id == moveOverItemKey) {
|
||||
moveOverItem = item
|
||||
}
|
||||
if (item.id == movedItemKey) {
|
||||
movedItem = item
|
||||
}
|
||||
if (moveOverItem != null && movedItem != null) {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return Pair(moveOverItem, movedItem)
|
||||
}
|
||||
|
||||
private fun checkCanMoveHeaderOver(item: OrganizeRowItemUM.Network, moveOverItem: OrganizeRowItemUM) =
|
||||
when (moveOverItem) {
|
||||
// Header can be moved only in its account
|
||||
is OrganizeRowItemUM.Placeholder -> item.accountId == moveOverItem.accountId
|
||||
else -> false
|
||||
}
|
||||
|
||||
private fun checkCanMoveTokenOver(
|
||||
item: OrganizeRowItemUM.Token,
|
||||
moveOverItem: OrganizeRowItemUM,
|
||||
isGrouped: Boolean,
|
||||
isAccountsMode: Boolean,
|
||||
): Boolean {
|
||||
return when (moveOverItem) {
|
||||
is OrganizeRowItemUM.Network -> false // Token item can not be moved to group item
|
||||
is OrganizeRowItemUM.Token -> when {
|
||||
// Token item can be moved only in its group
|
||||
isGrouped -> item.groupId == moveOverItem.groupId
|
||||
|
||||
// Token item can be moved only in its account
|
||||
isAccountsMode -> item.accountId == moveOverItem.accountId
|
||||
|
||||
// If ungrouped and not accounts mode then item can be moved anywhere
|
||||
else -> true
|
||||
}
|
||||
is OrganizeRowItemUM.Portfolio,
|
||||
is OrganizeRowItemUM.Placeholder,
|
||||
-> false // Token item can not be moved to portfolio or placeholder
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkIsItemsOrderChanged(tokensUM: OrganizeTokensUM): Boolean {
|
||||
fun OrganizeTokensUM?.getItemsIds(): List<Any>? = this?.tokenList?.mapNotNull { item ->
|
||||
if (item is OrganizeRowItemUM.Placeholder) {
|
||||
null
|
||||
} else {
|
||||
item.id
|
||||
}
|
||||
}
|
||||
|
||||
return tokensUM.getItemsIds() != draggingListState.getItemsIds()
|
||||
}
|
||||
|
||||
private fun List<OrganizeRowItemUM>.divideMovingItem(movingItem: OrganizeRowItemUM): List<OrganizeRowItemUM> {
|
||||
val mutableList = this.toMutableList()
|
||||
val listIterator = mutableList.listIterator()
|
||||
|
||||
while (listIterator.hasNext()) {
|
||||
val item = listIterator.next()
|
||||
|
||||
if (item.id == movingItem.id) {
|
||||
val dividedItem = movingItem
|
||||
.updateRoundingMode(RoundingModeUM.All())
|
||||
.updateShadowVisibility(show = true)
|
||||
|
||||
listIterator.set(dividedItem)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return mutableList
|
||||
}
|
||||
|
||||
data class DragOperation(
|
||||
val type: Type,
|
||||
val tokenList: PersistentList<OrganizeRowItemUM>,
|
||||
) {
|
||||
|
||||
sealed class Type {
|
||||
|
||||
data object Start : Type()
|
||||
|
||||
data object Dragged : Type()
|
||||
|
||||
data class End(val isItemsOrderChanged: Boolean) : Type()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
package com.tangem.feature.wallet.child.organizetokens.model.dnd
|
||||
|
||||
import com.tangem.feature.wallet.child.organizetokens.entity.DraggableItem
|
||||
import com.tangem.feature.wallet.child.organizetokens.entity.OrganizeRowItemUM
|
||||
import com.tangem.feature.wallet.child.organizetokens.entity.OrganizeTokensListUM
|
||||
import com.tangem.feature.wallet.child.organizetokens.model.DragAndDropIntents
|
||||
import com.tangem.feature.wallet.child.organizetokens.model.common.divideMovingItem
|
||||
|
|
@ -90,6 +91,8 @@ internal class DragAndDropAdapterLegacy(
|
|||
draggingListState = tokenListUM
|
||||
}
|
||||
|
||||
override fun onItemDraggingStart(item: OrganizeRowItemUM) { /* no-op */ }
|
||||
|
||||
override fun onItemDraggingEnd() {
|
||||
val draggingItem = draggingItem ?: return
|
||||
|
||||
|
|
|
|||
|
|
@ -1,11 +1,14 @@
|
|||
package com.tangem.feature.wallet.child.organizetokens.model.dnd
|
||||
|
||||
import com.tangem.feature.wallet.child.organizetokens.entity.DraggableItem
|
||||
import com.tangem.feature.wallet.child.organizetokens.entity.OrganizeRowItemUM
|
||||
import com.tangem.feature.wallet.child.organizetokens.model.common.divideMovingItem
|
||||
import com.tangem.feature.wallet.child.organizetokens.model.common.getGroupPlaceholder
|
||||
import com.tangem.feature.wallet.child.organizetokens.model.common.getGroupPlaceholderLegacy
|
||||
|
||||
internal class DraggableGroupsOperations {
|
||||
|
||||
private var groupIdToTokens: Map<String, List<OrganizeRowItemUM.Token>>? = null
|
||||
private var groupIdToTokensLegacy: Map<Int, List<DraggableItem.Token>>? = null
|
||||
|
||||
fun collapseGroupLegacy(items: List<DraggableItem>, movingGroup: DraggableItem.GroupHeader): List<DraggableItem> {
|
||||
|
|
@ -67,4 +70,67 @@ internal class DraggableGroupsOperations {
|
|||
|
||||
return expandedGroups
|
||||
}
|
||||
|
||||
fun expandGroups(items: List<OrganizeRowItemUM>, isAccountsMode: Boolean): List<OrganizeRowItemUM> {
|
||||
if (groupIdToTokens.isNullOrEmpty()) return items
|
||||
|
||||
val accountList = items.filterIsInstance<OrganizeRowItemUM.Portfolio>()
|
||||
val currentGroups = items.filterIsInstance<OrganizeRowItemUM.Network>()
|
||||
|
||||
val expandedGroups = if (isAccountsMode) {
|
||||
accountList
|
||||
.asSequence()
|
||||
.flatMap { account ->
|
||||
buildList {
|
||||
add(account)
|
||||
currentGroups
|
||||
.asSequence()
|
||||
.filter { it.accountId == account.id }
|
||||
.forEachIndexed { index, group ->
|
||||
if (index == 0) {
|
||||
add(getGroupPlaceholder(accountId = group.accountId, index = -1))
|
||||
}
|
||||
add(group)
|
||||
addAll(groupIdToTokens?.get(group.id).orEmpty())
|
||||
add(getGroupPlaceholder(accountId = group.accountId, index = index))
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
currentGroups
|
||||
.asSequence()
|
||||
.flatMapIndexed { index, group ->
|
||||
buildList {
|
||||
if (index == 0) {
|
||||
add(getGroupPlaceholder(accountId = group.accountId, index = -1))
|
||||
}
|
||||
add(group)
|
||||
addAll(groupIdToTokens?.get(group.id).orEmpty())
|
||||
add(getGroupPlaceholder(accountId = group.accountId, index = index))
|
||||
}
|
||||
}
|
||||
}.toList()
|
||||
|
||||
groupIdToTokens = null
|
||||
|
||||
return expandedGroups
|
||||
}
|
||||
|
||||
fun collapseGroup(
|
||||
items: List<OrganizeRowItemUM>,
|
||||
movingGroup: OrganizeRowItemUM.Network,
|
||||
): List<OrganizeRowItemUM> {
|
||||
if (!groupIdToTokens.isNullOrEmpty()) return items
|
||||
|
||||
groupIdToTokens = items
|
||||
.asSequence()
|
||||
.filterIsInstance<OrganizeRowItemUM.Token>()
|
||||
.groupBy { it.groupId }
|
||||
|
||||
val itemsWithoutGroupTokens = items.filterNot {
|
||||
it is OrganizeRowItemUM.Token && it.groupId == movingGroup.id
|
||||
}
|
||||
|
||||
return itemsWithoutGroupTokens
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
package com.tangem.feature.wallet.child.organizetokens.model.transformer
|
||||
|
||||
import com.tangem.domain.account.models.AccountStatusList
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.models.TokensGroupType
|
||||
import com.tangem.domain.models.TokensSortType
|
||||
import com.tangem.feature.wallet.child.organizetokens.entity.OrganizeTokensUM
|
||||
import com.tangem.feature.wallet.child.organizetokens.model.converter.OrganizeTokensListConverter
|
||||
import com.tangem.utils.transformer.Transformer
|
||||
|
||||
internal class OrganizeContentStateTransformer(
|
||||
private val accountStatusList: AccountStatusList,
|
||||
private val isAccountsMode: Boolean,
|
||||
private val appCurrency: AppCurrency,
|
||||
) : Transformer<OrganizeTokensUM> {
|
||||
|
||||
private val tokenListConverter by lazy(LazyThreadSafetyMode.NONE) {
|
||||
OrganizeTokensListConverter(
|
||||
isAccountsMode = isAccountsMode,
|
||||
appCurrency = appCurrency,
|
||||
)
|
||||
}
|
||||
|
||||
override fun transform(prevState: OrganizeTokensUM): OrganizeTokensUM {
|
||||
val isGrouping = accountStatusList.groupType == TokensGroupType.NETWORK
|
||||
val isSortedByBalance = accountStatusList.sortType == TokensSortType.BALANCE
|
||||
|
||||
return prevState.copy(
|
||||
isGrouped = isGrouping,
|
||||
isAccountsMode = isAccountsMode,
|
||||
tokenList = tokenListConverter.convert(value = accountStatusList),
|
||||
organizeMenuUM = prevState.organizeMenuUM.copy(
|
||||
isEnabled = prevState.tokenList.isNotEmpty(),
|
||||
isSortedByBalance = isSortedByBalance,
|
||||
isGrouped = isGrouping,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
package com.tangem.feature.wallet.child.organizetokens.model.transformer
|
||||
|
||||
import com.tangem.feature.wallet.child.organizetokens.entity.OrganizeTokensUM
|
||||
import com.tangem.utils.transformer.Transformer
|
||||
|
||||
internal object OrganizeDisableBalanceSortingTransformer : Transformer<OrganizeTokensUM> {
|
||||
override fun transform(prevState: OrganizeTokensUM): OrganizeTokensUM {
|
||||
return prevState.copy(
|
||||
organizeMenuUM = prevState.organizeMenuUM.copy(
|
||||
isSortedByBalance = false,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
package com.tangem.feature.wallet.child.organizetokens.model.transformer
|
||||
|
||||
import com.tangem.feature.wallet.child.organizetokens.entity.OrganizeTokensUM
|
||||
import com.tangem.utils.transformer.Transformer
|
||||
|
||||
internal class OrganizeSortingProgressStateTransformer(
|
||||
private val isSortingInProgress: Boolean,
|
||||
) : Transformer<OrganizeTokensUM> {
|
||||
override fun transform(prevState: OrganizeTokensUM): OrganizeTokensUM {
|
||||
return prevState.copy(
|
||||
organizeMenuUM = prevState.organizeMenuUM.copy(
|
||||
isEnabled = !isSortingInProgress,
|
||||
),
|
||||
cancelButton = prevState.cancelButton.copy(
|
||||
isEnabled = !isSortingInProgress,
|
||||
),
|
||||
applyButton = prevState.applyButton.copy(
|
||||
isLoading = isSortingInProgress,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
package com.tangem.feature.wallet.child.organizetokens.ui
|
||||
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.DpOffset
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.tangem.core.ui.ds.contextmenu.TangemContextMenu
|
||||
import com.tangem.core.ui.ds.contextmenu.TangemContextMenuCheckboxItem
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.feature.wallet.child.organizetokens.entity.OrganizeTokensUM
|
||||
import com.tangem.feature.wallet.impl.R
|
||||
|
||||
@Composable
|
||||
internal fun OrganizeDropDownMenu(
|
||||
organizeMenuUM: OrganizeTokensUM.OrganizeMenuUM,
|
||||
showDropdownMenu: Boolean,
|
||||
onDropdownDismiss: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
TangemContextMenu(
|
||||
expanded = showDropdownMenu,
|
||||
onDismissRequest = onDropdownDismiss,
|
||||
offset = DpOffset.Zero,
|
||||
modifier = modifier,
|
||||
) {
|
||||
TangemContextMenuCheckboxItem(
|
||||
title = TextReference.Res(R.string.organize_tokens_sort_by_balance),
|
||||
isChecked = organizeMenuUM.isSortedByBalance,
|
||||
onClick = organizeMenuUM.onSortClick,
|
||||
)
|
||||
HorizontalDivider(
|
||||
thickness = 0.5.dp,
|
||||
color = TangemTheme.colors2.border.neutral.quaternary,
|
||||
)
|
||||
TangemContextMenuCheckboxItem(
|
||||
title = TextReference.Res(R.string.organize_tokens_group),
|
||||
isChecked = organizeMenuUM.isGrouped,
|
||||
onClick = organizeMenuUM.onGroupClick,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,362 @@
|
|||
package com.tangem.feature.wallet.child.organizetokens.ui
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.animation.core.animateDpAsState
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.LazyItemScope
|
||||
import androidx.compose.foundation.lazy.itemsIndexed
|
||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.composed
|
||||
import androidx.compose.ui.draw.shadow
|
||||
import androidx.compose.ui.graphics.RectangleShape
|
||||
import androidx.compose.ui.graphics.Shape
|
||||
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.platform.LocalHapticFeedback
|
||||
import androidx.compose.ui.platform.testTag
|
||||
import androidx.compose.ui.semantics.semantics
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import com.tangem.core.ui.components.SpacerH
|
||||
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.components.haze.hazeEffectTangem
|
||||
import com.tangem.core.ui.components.haze.hazeSourceTangem
|
||||
import com.tangem.core.ui.ds.button.TangemButton
|
||||
import com.tangem.core.ui.ds.row.header.TangemHeaderRow
|
||||
import com.tangem.core.ui.ds.row.token.TangemTokenRow
|
||||
import com.tangem.core.ui.ds.topbar.TangemTopBar
|
||||
import com.tangem.core.ui.ds.topbar.TangemTopBarActionContent
|
||||
import com.tangem.core.ui.ds.topbar.TangemTopBarActionUM
|
||||
import com.tangem.core.ui.ds.topbar.TangemTopBarType
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.reordarable.ReorderableItem
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreviewRedesign
|
||||
import com.tangem.core.ui.test.OrganizeTokensScreenTestTags
|
||||
import com.tangem.core.ui.utils.lazyListItemPosition
|
||||
import com.tangem.feature.wallet.child.organizetokens.entity.DraggableItem
|
||||
import com.tangem.feature.wallet.child.organizetokens.entity.OrganizeRowItemUM
|
||||
import com.tangem.feature.wallet.child.organizetokens.entity.OrganizeTokensUM
|
||||
import com.tangem.feature.wallet.child.organizetokens.entity.RoundingModeUM
|
||||
import com.tangem.feature.wallet.child.organizetokens.model.DragAndDropIntents
|
||||
import com.tangem.feature.wallet.child.organizetokens.ui.preview.OrganizeTokensPreview
|
||||
import com.tangem.feature.wallet.impl.R
|
||||
import dev.chrisbanes.haze.rememberHazeState
|
||||
import org.burnoutcrew.reorderable.ItemPosition
|
||||
import org.burnoutcrew.reorderable.ReorderableLazyListState
|
||||
import org.burnoutcrew.reorderable.rememberReorderableLazyListState
|
||||
import org.burnoutcrew.reorderable.reorderable
|
||||
|
||||
@Composable
|
||||
internal fun OrganizeTokensContent(
|
||||
organizeTokensUM: OrganizeTokensUM,
|
||||
dragAndDropIntents: DragAndDropIntents,
|
||||
onDismiss: () -> Unit,
|
||||
) {
|
||||
var isShowDropdownMenu by rememberSaveable { mutableStateOf(false) }
|
||||
|
||||
val hazeState = rememberHazeState()
|
||||
|
||||
TangemBottomSheet<TangemBottomSheetConfigContent.Empty>(
|
||||
config = TangemBottomSheetConfig(
|
||||
isShown = true,
|
||||
onDismissRequest = onDismiss,
|
||||
content = TangemBottomSheetConfigContent.Empty,
|
||||
),
|
||||
containerColor = TangemTheme.colors2.surface.level2,
|
||||
title = {
|
||||
TangemTopBar(
|
||||
title = resourceReference(R.string.organize_tokens_title),
|
||||
type = TangemTopBarType.BottomSheet,
|
||||
endContent = {
|
||||
TangemTopBarActionContent(
|
||||
actionUM = TangemTopBarActionUM(
|
||||
iconRes = R.drawable.ic_exchange_mini_24,
|
||||
isActionable = true,
|
||||
onClick = { isShowDropdownMenu = true },
|
||||
ghostModeProgress = 0f,
|
||||
),
|
||||
type = TangemTopBarType.BottomSheet,
|
||||
)
|
||||
OrganizeDropDownMenu(
|
||||
organizeMenuUM = organizeTokensUM.organizeMenuUM,
|
||||
showDropdownMenu = isShowDropdownMenu,
|
||||
onDropdownDismiss = { isShowDropdownMenu = false },
|
||||
modifier = Modifier.hazeEffectTangem(hazeState),
|
||||
)
|
||||
},
|
||||
)
|
||||
},
|
||||
footer = {
|
||||
BottomButtons(organizeTokensUM = organizeTokensUM)
|
||||
},
|
||||
content = {
|
||||
TokenList(
|
||||
organizeTokensUM = organizeTokensUM,
|
||||
dragAndDropIntents = dragAndDropIntents,
|
||||
modifier = Modifier.hazeSourceTangem(hazeState, zIndex = -1f),
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
@Composable
|
||||
private fun TokenList(
|
||||
organizeTokensUM: OrganizeTokensUM,
|
||||
dragAndDropIntents: DragAndDropIntents,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val tokensListState = rememberLazyListState()
|
||||
|
||||
val hapticFeedback = LocalHapticFeedback.current
|
||||
val tokenList = organizeTokensUM.tokenList
|
||||
Box(
|
||||
modifier = modifier.background(TangemTheme.colors2.surface.level2),
|
||||
) {
|
||||
val onDragEnd: (Int, Int) -> Unit = remember {
|
||||
{ _, _ ->
|
||||
dragAndDropIntents.onItemDraggingEnd()
|
||||
}
|
||||
}
|
||||
val reorderableListState = rememberReorderableLazyListState(
|
||||
onMove = dragAndDropIntents::onItemDragged,
|
||||
listState = tokensListState,
|
||||
canDragOver = dragAndDropIntents::canDragItemOver,
|
||||
onDragEnd = onDragEnd,
|
||||
)
|
||||
|
||||
val bottomBarHeight = with(LocalDensity.current) { WindowInsets.systemBars.getBottom(this).toDp() }
|
||||
|
||||
val listContentPadding = PaddingValues(
|
||||
top = TangemTheme.dimens2.x1,
|
||||
bottom = TangemTheme.dimens2.x1 + bottomBarHeight,
|
||||
start = TangemTheme.dimens2.x3,
|
||||
end = TangemTheme.dimens2.x3,
|
||||
)
|
||||
|
||||
LazyColumn(
|
||||
modifier = Modifier
|
||||
.align(Alignment.TopCenter)
|
||||
.reorderable(reorderableListState)
|
||||
.testTag(OrganizeTokensScreenTestTags.TOKENS_LAZY_LIST)
|
||||
.hazeSourceTangem(zIndex = 1f),
|
||||
state = reorderableListState.listState,
|
||||
contentPadding = listContentPadding,
|
||||
) {
|
||||
itemsIndexed(
|
||||
items = tokenList,
|
||||
key = { _, item -> item.id },
|
||||
) { index, item ->
|
||||
|
||||
val onDragStart = remember(item) {
|
||||
{
|
||||
dragAndDropIntents.onItemDraggingStart(item)
|
||||
hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress)
|
||||
}
|
||||
}
|
||||
|
||||
DraggableItem(
|
||||
index = index,
|
||||
item = item,
|
||||
reorderableState = reorderableListState,
|
||||
onDragStart = onDragStart,
|
||||
isBalanceHidden = organizeTokensUM.isBalanceHidden,
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
SpacerH(TangemTheme.dimens2.x20)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun LazyItemScope.DraggableItem(
|
||||
index: Int,
|
||||
item: OrganizeRowItemUM,
|
||||
reorderableState: ReorderableLazyListState,
|
||||
onDragStart: () -> Unit,
|
||||
isBalanceHidden: Boolean,
|
||||
) {
|
||||
var isDragging by remember {
|
||||
mutableStateOf(value = false)
|
||||
}
|
||||
|
||||
val itemModifier = Modifier.applyShapeAndShadow(item.roundingModeUM, item.isShowShadow)
|
||||
|
||||
ReorderableItem(
|
||||
reorderableState = reorderableState,
|
||||
index = index,
|
||||
key = item.id,
|
||||
) { isItemDragging ->
|
||||
isDragging = isItemDragging
|
||||
|
||||
val modifierWithBackground = itemModifier
|
||||
.background(color = TangemTheme.colors.background.primary)
|
||||
.semantics { lazyListItemPosition = index }
|
||||
|
||||
when (item) {
|
||||
is OrganizeRowItemUM.Network -> TangemHeaderRow(
|
||||
modifier = modifierWithBackground,
|
||||
reorderableState = reorderableState,
|
||||
headerRowUM = item.headerRowUM,
|
||||
)
|
||||
is OrganizeRowItemUM.Portfolio -> TangemHeaderRow(
|
||||
modifier = modifierWithBackground,
|
||||
headerRowUM = item.headerRowUM,
|
||||
isBalanceHidden = isBalanceHidden,
|
||||
)
|
||||
is OrganizeRowItemUM.Token -> TangemTokenRow(
|
||||
modifier = modifierWithBackground,
|
||||
tokenRowUM = item.tokenRowUM,
|
||||
reorderableState = reorderableState,
|
||||
isBalanceHidden = isBalanceHidden,
|
||||
)
|
||||
// Should be presented in the list but remain invisible
|
||||
is OrganizeRowItemUM.Placeholder -> Box(modifier = Modifier.fillMaxWidth())
|
||||
}
|
||||
}
|
||||
|
||||
DisposableEffect(isDragging) {
|
||||
onDispose {
|
||||
if (isDragging) {
|
||||
onDragStart()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun BoxScope.BottomButtons(organizeTokensUM: OrganizeTokensUM) {
|
||||
val bottomBarHeight = with(LocalDensity.current) { WindowInsets.systemBars.getBottom(this).toDp() }
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens2.x2),
|
||||
modifier = Modifier
|
||||
.align(Alignment.BottomCenter)
|
||||
.padding(horizontal = TangemTheme.dimens2.x4)
|
||||
.padding(bottom = bottomBarHeight + TangemTheme.dimens2.x4),
|
||||
) {
|
||||
TangemButton(
|
||||
buttonUM = organizeTokensUM.cancelButton,
|
||||
modifier = Modifier.weight(1f),
|
||||
)
|
||||
TangemButton(
|
||||
buttonUM = organizeTokensUM.applyButton,
|
||||
modifier = Modifier.weight(1f),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun Modifier.applyShapeAndShadow(roundingMode: RoundingModeUM, showShadow: Boolean): Modifier {
|
||||
return composed {
|
||||
val radius by animateDpAsState(
|
||||
targetValue = when (roundingMode) {
|
||||
is RoundingModeUM.None -> TangemTheme.dimens2.x0
|
||||
is RoundingModeUM.All -> TangemTheme.dimens2.x3
|
||||
is RoundingModeUM.Bottom,
|
||||
is RoundingModeUM.Top,
|
||||
-> TangemTheme.dimens2.x4
|
||||
},
|
||||
label = "item_shape_radius",
|
||||
)
|
||||
val elevation by animateDpAsState(
|
||||
targetValue = if (showShadow) {
|
||||
TangemTheme.dimens2.x2
|
||||
} else {
|
||||
TangemTheme.dimens2.x0
|
||||
},
|
||||
label = "item_elevation",
|
||||
)
|
||||
|
||||
this
|
||||
.padding(paddingValues = getItemGap(roundingMode))
|
||||
.shadow(
|
||||
elevation = elevation,
|
||||
shape = getItemShape(roundingMode, radius),
|
||||
clip = true,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
@ReadOnlyComposable
|
||||
private fun getItemGap(roundingMode: RoundingModeUM): PaddingValues {
|
||||
val paddingValue = TangemTheme.dimens2.x1
|
||||
|
||||
return if (roundingMode.isShowGap) {
|
||||
when (roundingMode) {
|
||||
is RoundingModeUM.None -> PaddingValues(all = TangemTheme.dimens2.x0)
|
||||
is RoundingModeUM.All -> PaddingValues(vertical = paddingValue)
|
||||
is RoundingModeUM.Top -> PaddingValues(top = paddingValue)
|
||||
is RoundingModeUM.Bottom -> PaddingValues(bottom = paddingValue)
|
||||
}
|
||||
} else {
|
||||
PaddingValues(all = TangemTheme.dimens2.x0)
|
||||
}
|
||||
}
|
||||
|
||||
@Stable
|
||||
private fun getItemShape(roundingMode: RoundingModeUM, radius: Dp): Shape {
|
||||
return when (roundingMode) {
|
||||
is RoundingModeUM.None -> RectangleShape
|
||||
is RoundingModeUM.Top -> RoundedCornerShape(
|
||||
topStart = radius,
|
||||
topEnd = radius,
|
||||
)
|
||||
is RoundingModeUM.Bottom -> RoundedCornerShape(
|
||||
bottomStart = radius,
|
||||
bottomEnd = radius,
|
||||
)
|
||||
is RoundingModeUM.All -> RoundedCornerShape(
|
||||
size = radius,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// region Preview
|
||||
@Composable
|
||||
@Preview(showBackground = true, widthDp = 360)
|
||||
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
private fun OrganizeTokensContent_Preview(
|
||||
@PreviewParameter(OrganizeTokensContentPreviewProvider::class) params: OrganizeTokensUM,
|
||||
) {
|
||||
TangemThemePreviewRedesign {
|
||||
OrganizeTokensContent(
|
||||
organizeTokensUM = params,
|
||||
dragAndDropIntents = object : DragAndDropIntents {
|
||||
override fun onItemDragged(from: ItemPosition, to: ItemPosition) {}
|
||||
|
||||
override fun canDragItemOver(dragOver: ItemPosition, dragging: ItemPosition): Boolean = false
|
||||
|
||||
override fun onItemDraggingStartLegacy(item: DraggableItem) {}
|
||||
|
||||
override fun onItemDraggingStart(item: OrganizeRowItemUM) {}
|
||||
|
||||
override fun onItemDraggingEnd() {}
|
||||
},
|
||||
onDismiss = {},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private class OrganizeTokensContentPreviewProvider : PreviewParameterProvider<OrganizeTokensUM> {
|
||||
override val values: Sequence<OrganizeTokensUM>
|
||||
get() = sequenceOf(
|
||||
OrganizeTokensPreview.defaultState,
|
||||
OrganizeTokensPreview.defaultState.copy(isGrouped = false),
|
||||
)
|
||||
}
|
||||
// endregion
|
||||
|
|
@ -0,0 +1,129 @@
|
|||
package com.tangem.feature.wallet.child.organizetokens.ui.preview
|
||||
|
||||
import com.tangem.core.ui.components.currency.icon.CurrencyIconState
|
||||
import com.tangem.core.ui.ds.button.TangemButtonType
|
||||
import com.tangem.core.ui.ds.button.TangemButtonUM
|
||||
import com.tangem.core.ui.ds.image.TangemIconUM
|
||||
import com.tangem.core.ui.ds.row.header.TangemHeaderRowUM
|
||||
import com.tangem.core.ui.ds.row.internal.TangemRowTailUM
|
||||
import com.tangem.core.ui.ds.row.token.TangemTokenRowUM
|
||||
import com.tangem.core.ui.event.consumedEvent
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.core.ui.res.TangemColorPalette
|
||||
import com.tangem.feature.wallet.child.organizetokens.entity.OrganizeRowItemUM
|
||||
import com.tangem.feature.wallet.child.organizetokens.entity.OrganizeTokensUM
|
||||
import com.tangem.feature.wallet.child.organizetokens.entity.RoundingModeUM
|
||||
import com.tangem.feature.wallet.impl.R
|
||||
import kotlinx.collections.immutable.toPersistentList
|
||||
import java.util.UUID
|
||||
|
||||
internal object OrganizeTokensPreview {
|
||||
|
||||
private const val networksSize = 10
|
||||
private const val tokensSize = 3
|
||||
|
||||
private val draggableToken = TangemTokenRowUM.Actionable(
|
||||
id = UUID.randomUUID().toString(),
|
||||
headIconUM = TangemIconUM.Currency(
|
||||
CurrencyIconState.TokenIcon(
|
||||
url = null,
|
||||
topBadgeIconResId = R.drawable.img_polygon_22,
|
||||
fallbackTint = TangemColorPalette.Black,
|
||||
fallbackBackground = TangemColorPalette.Meadow,
|
||||
isGrayscale = false,
|
||||
shouldShowCustomBadge = false,
|
||||
),
|
||||
),
|
||||
titleUM = TangemTokenRowUM.TitleUM.Content(stringReference(value = "Polygon")),
|
||||
subtitleUM = TangemTokenRowUM.SubtitleUM.Empty,
|
||||
topEndContentUM = TangemTokenRowUM.EndContentUM.Content(
|
||||
text = stringReference("$ 42,900.13"),
|
||||
),
|
||||
bottomEndContentUM = TangemTokenRowUM.EndContentUM.Content(
|
||||
text = stringReference("733,71097 POL"),
|
||||
),
|
||||
tailUM = TangemRowTailUM.Draggable(R.drawable.ic_group_drop_24),
|
||||
onItemClick = null,
|
||||
onItemLongClick = null,
|
||||
)
|
||||
|
||||
private val tokenList = List(networksSize) { it }
|
||||
.flatMap { index ->
|
||||
val lastNetworkIndex = networksSize - 1
|
||||
val lastTokenIndex = tokensSize - 1
|
||||
val networkNumber = index + 1
|
||||
|
||||
val group = OrganizeRowItemUM.Network(
|
||||
headerRowUM = TangemHeaderRowUM(
|
||||
id = networkNumber.toString(),
|
||||
title = stringReference(value = "$networkNumber"),
|
||||
),
|
||||
roundingModeUM = when (index) {
|
||||
0 -> RoundingModeUM.Top()
|
||||
lastNetworkIndex -> RoundingModeUM.Bottom()
|
||||
else -> RoundingModeUM.None
|
||||
},
|
||||
accountId = "account_$networkNumber",
|
||||
)
|
||||
|
||||
val tokens: MutableList<OrganizeRowItemUM.Token> = mutableListOf()
|
||||
repeat(times = tokensSize) { i ->
|
||||
val tokenNumber = i + 1
|
||||
tokens.add(
|
||||
OrganizeRowItemUM.Token(
|
||||
tokenRowUM = draggableToken.copy(
|
||||
id = "${group.id}_token_$tokenNumber",
|
||||
titleUM = TangemTokenRowUM.TitleUM.Content(
|
||||
text = stringReference(value = "Token $tokenNumber from $networkNumber network"),
|
||||
),
|
||||
),
|
||||
groupId = group.id,
|
||||
accountId = "account_$networkNumber",
|
||||
roundingModeUM = when {
|
||||
i == lastTokenIndex && index == lastNetworkIndex -> RoundingModeUM.Bottom()
|
||||
else -> RoundingModeUM.None
|
||||
},
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
val divider = OrganizeRowItemUM.Placeholder(
|
||||
id = "divider_$networkNumber",
|
||||
accountId = "account_$networkNumber",
|
||||
)
|
||||
|
||||
buildList {
|
||||
add(group)
|
||||
addAll(tokens)
|
||||
if (index != lastNetworkIndex) {
|
||||
add(divider)
|
||||
}
|
||||
}
|
||||
}
|
||||
.toPersistentList()
|
||||
|
||||
val defaultState by lazy {
|
||||
OrganizeTokensUM(
|
||||
tokenList = tokenList,
|
||||
organizeMenuUM = OrganizeTokensUM.OrganizeMenuUM(
|
||||
onSortClick = {},
|
||||
onGroupClick = {},
|
||||
),
|
||||
cancelButton = TangemButtonUM(
|
||||
text = resourceReference(R.string.common_cancel),
|
||||
onClick = {},
|
||||
type = TangemButtonType.Secondary,
|
||||
),
|
||||
applyButton = TangemButtonUM(
|
||||
text = resourceReference(R.string.common_apply),
|
||||
onClick = {},
|
||||
type = TangemButtonType.Primary,
|
||||
),
|
||||
scrollListToTop = consumedEvent(),
|
||||
isAccountsMode = true,
|
||||
isBalanceHidden = true,
|
||||
isGrouped = false,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -16,11 +16,14 @@ import com.tangem.core.decompose.context.child
|
|||
import com.tangem.core.decompose.context.childByContext
|
||||
import com.tangem.core.decompose.model.getOrCreateModel
|
||||
import com.tangem.core.ui.DesignFeatureToggles
|
||||
import com.tangem.features.promobanners.api.NewPromoBannersFeatureToggles
|
||||
import com.tangem.features.promobanners.api.PromoBannersBlockComponent
|
||||
import com.tangem.core.ui.components.bottomsheets.state.BottomSheetState
|
||||
import com.tangem.core.ui.decompose.ComposableBottomSheetComponent
|
||||
import com.tangem.core.ui.decompose.ComposableContentComponent
|
||||
import com.tangem.core.ui.decompose.ComposableDialogComponent
|
||||
import com.tangem.domain.tokens.model.details.TokenAction
|
||||
import com.tangem.feature.wallet.child.organizetokens.OrganizeTokensComponent
|
||||
import com.tangem.feature.wallet.child.tokenActions.TokenActionsComponent
|
||||
import com.tangem.feature.wallet.child.wallet.model.WalletModel
|
||||
import com.tangem.feature.wallet.navigation.WalletRoute
|
||||
|
|
@ -29,10 +32,12 @@ import com.tangem.feature.wallet.presentation.wallet.ui.WalletScreen
|
|||
import com.tangem.feature.wallet.presentation.wallet.ui.WalletScreen2
|
||||
import com.tangem.feature.wallet.presentation.wallet.ui.components.visa.KycRejectedComponent
|
||||
import com.tangem.feature.walletsettings.component.RenameWalletComponent
|
||||
import com.tangem.core.ui.utils.parseBigDecimal
|
||||
import com.tangem.features.biometry.AskBiometryComponent
|
||||
import com.tangem.features.feed.entry.components.FeedEntryComponent
|
||||
import com.tangem.features.pushnotifications.api.PushNotificationsBottomSheetComponent
|
||||
import com.tangem.features.pushnotifications.api.PushNotificationsParams
|
||||
import com.tangem.features.send.v2.api.NetworkSelectionComponent
|
||||
import com.tangem.features.tokenreceive.TokenReceiveComponent
|
||||
import com.tangem.features.yield.supply.api.YieldSupplyDepositedWarningComponent
|
||||
import dagger.assisted.Assisted
|
||||
|
|
@ -51,6 +56,9 @@ internal class WalletComponent @AssistedInject constructor(
|
|||
private val pushNotificationsBottomSheetComponent: PushNotificationsBottomSheetComponent.Factory,
|
||||
private val tokenReceiveComponentFactory: TokenReceiveComponent.Factory,
|
||||
private val yieldSupplyDepositedWarningComponent: YieldSupplyDepositedWarningComponent.Factory,
|
||||
private val promoBannersBlockComponentFactory: PromoBannersBlockComponent.Factory,
|
||||
private val newPromoBannersFeatureToggles: NewPromoBannersFeatureToggles,
|
||||
private val networkSelectionComponentFactory: NetworkSelectionComponent.Factory,
|
||||
private val designFeatureToggles: DesignFeatureToggles,
|
||||
) : ComposableContentComponent, AppComponentContext by appComponentContext {
|
||||
|
||||
|
|
@ -63,6 +71,16 @@ internal class WalletComponent @AssistedInject constructor(
|
|||
)
|
||||
}
|
||||
|
||||
private val promoBannersBlockComponent: PromoBannersBlockComponent? by lazy {
|
||||
if (!newPromoBannersFeatureToggles.isNewPromoBannersEnabled) return@lazy null
|
||||
promoBannersBlockComponentFactory.create(
|
||||
context = child("promoBannersBlockComponent"),
|
||||
params = PromoBannersBlockComponent.Params(
|
||||
placeholder = PromoBannersBlockComponent.Placeholder.MAIN,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
init {
|
||||
lifecycle.subscribe(model.screenLifecycleProvider)
|
||||
doOnResume { model.onResume() }
|
||||
|
|
@ -146,6 +164,51 @@ internal class WalletComponent @AssistedInject constructor(
|
|||
),
|
||||
)
|
||||
}
|
||||
is WalletDialogConfig.OrganizeTokens -> {
|
||||
OrganizeTokensComponent(
|
||||
appComponentContext = childByContext(componentContext),
|
||||
params = OrganizeTokensComponent.Params(
|
||||
userWalletId = dialogConfig.userWalletId,
|
||||
callback = model.innerWalletRouter.organizeCallbacks,
|
||||
),
|
||||
)
|
||||
}
|
||||
is WalletDialogConfig.NetworkSelection -> {
|
||||
networkSelectionComponentFactory.create(
|
||||
context = childByContext(componentContext),
|
||||
params = NetworkSelectionComponent.Params(
|
||||
address = dialogConfig.address,
|
||||
amount = dialogConfig.amount,
|
||||
memo = dialogConfig.memo,
|
||||
walletGroups = dialogConfig.walletGroups.map { walletGroup ->
|
||||
NetworkSelectionComponent.Params.WalletGroup(
|
||||
userWalletId = walletGroup.userWalletId,
|
||||
walletName = walletGroup.walletName,
|
||||
accounts = walletGroup.accounts.map { accountGroup ->
|
||||
NetworkSelectionComponent.Params.AccountGroup(
|
||||
accountId = accountGroup.accountId,
|
||||
accountName = accountGroup.accountName,
|
||||
currencies = accountGroup.currencies,
|
||||
hiddenTokensCount = accountGroup.hiddenTokensCount,
|
||||
)
|
||||
},
|
||||
)
|
||||
},
|
||||
onTokenSelected = { userWalletId, currency ->
|
||||
model.innerWalletRouter.dialogNavigation.dismiss()
|
||||
model.innerWalletRouter.openSend(
|
||||
userWalletId = userWalletId,
|
||||
currency = currency,
|
||||
address = dialogConfig.address,
|
||||
amount = dialogConfig.amount?.parseBigDecimal(currency.decimals),
|
||||
tag = dialogConfig.memo,
|
||||
entryType = AppRoute.Send.EntryType.Manual,
|
||||
)
|
||||
},
|
||||
onDismiss = model.innerWalletRouter.dialogNavigation::dismiss,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
|
|
@ -172,6 +235,7 @@ internal class WalletComponent @AssistedInject constructor(
|
|||
} else {
|
||||
WalletScreen(
|
||||
state = model.uiState.collectAsStateWithLifecycle().value,
|
||||
promoBannersBlockComponent = promoBannersBlockComponent,
|
||||
bottomSheetContent = {
|
||||
BottomSheetContent(
|
||||
bottomSheetState = bottomSheetState,
|
||||
|
|
|
|||
|
|
@ -4,12 +4,15 @@ import androidx.compose.runtime.Stable
|
|||
import arrow.core.getOrElse
|
||||
import com.arkivanov.decompose.router.slot.activate
|
||||
import com.arkivanov.decompose.router.slot.dismiss
|
||||
import com.tangem.common.routing.AppRoute
|
||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||
import com.tangem.core.ui.utils.parseBigDecimal
|
||||
import com.tangem.core.analytics.models.AnalyticsParam
|
||||
import com.tangem.core.analytics.models.event.MainScreenAnalyticsEvent
|
||||
import com.tangem.core.analytics.utils.TrackingContextProxy
|
||||
import com.tangem.core.decompose.di.ModelScoped
|
||||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.core.decompose.ui.UiMessageSender
|
||||
import com.tangem.datasource.local.appsflyer.AppsFlyerStore
|
||||
import com.tangem.domain.account.supplier.SingleAccountListSupplier
|
||||
import com.tangem.domain.account.usecase.IsAccountsModeEnabledUseCase
|
||||
|
|
@ -22,8 +25,16 @@ import com.tangem.domain.common.wallets.UserWalletsListRepository
|
|||
import com.tangem.domain.models.wallet.*
|
||||
import com.tangem.domain.notifications.GetIsHuaweiDeviceWithoutGoogleServicesUseCase
|
||||
import com.tangem.domain.notifications.repository.NotificationsRepository
|
||||
import com.tangem.domain.qrscanning.models.QrResultSource
|
||||
import com.tangem.domain.qrscanning.models.SourceType
|
||||
import com.tangem.domain.qrscanning.usecases.ListenToQrScanningUseCase
|
||||
import com.tangem.domain.qrscanning.models.ClassifiedQrContent
|
||||
import com.tangem.domain.qrscanning.models.QrSendTarget
|
||||
import com.tangem.domain.walletconnect.WcPairService
|
||||
import com.tangem.domain.walletconnect.model.WcPairRequest
|
||||
import com.tangem.domain.pay.repository.OnboardingRepository
|
||||
import com.tangem.domain.pay.usecase.TangemPayMainScreenCustomerInfoUseCase
|
||||
import com.tangem.domain.qrscanning.usecases.ResolveQrSendTargetsUseCase
|
||||
import com.tangem.domain.settings.*
|
||||
import com.tangem.domain.tokens.RefreshMultiCurrencyWalletQuotesUseCase
|
||||
import com.tangem.domain.wallets.usecase.*
|
||||
|
|
@ -39,6 +50,7 @@ import com.tangem.feature.wallet.presentation.wallet.domain.WalletImageResolver
|
|||
import com.tangem.feature.wallet.presentation.wallet.domain.WalletNameMigrationUseCase
|
||||
import com.tangem.feature.wallet.presentation.wallet.loaders.WalletScreenContentLoader
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletAlertUM
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletDialogConfig
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletEvent
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletEvent.DemonstrateWalletsScrollPreview.Direction
|
||||
|
|
@ -50,6 +62,7 @@ import com.tangem.feature.wallet.presentation.wallet.utils.ScreenLifecycleProvid
|
|||
import com.tangem.features.biometry.AskBiometryComponent
|
||||
import com.tangem.features.pushnotifications.api.PushNotificationsModelCallbacks
|
||||
import com.tangem.features.wallet.deeplink.WalletDeepLinkActionListener
|
||||
import com.tangem.features.wallet.featuretoggles.WalletFeatureToggles
|
||||
import com.tangem.utils.Provider
|
||||
import com.tangem.utils.coroutines.*
|
||||
import kotlinx.coroutines.*
|
||||
|
|
@ -102,6 +115,11 @@ internal class WalletModel @Inject constructor(
|
|||
private val appsFlyerStore: AppsFlyerStore,
|
||||
private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
|
||||
private val getWalletIconUseCase: GetWalletIconUseCase,
|
||||
private val walletFeatureToggles: WalletFeatureToggles,
|
||||
private val listenToQrScanningUseCase: ListenToQrScanningUseCase,
|
||||
private val wcPairService: WcPairService,
|
||||
private val resolveQrSendTargetsUseCase: ResolveQrSendTargetsUseCase,
|
||||
private val uiMessageSender: UiMessageSender,
|
||||
val screenLifecycleProvider: ScreenLifecycleProvider,
|
||||
val innerWalletRouter: InnerWalletRouter,
|
||||
) : Model() {
|
||||
|
|
@ -132,6 +150,7 @@ internal class WalletModel @Inject constructor(
|
|||
subscribeToScreenBackgroundState()
|
||||
subscribeOnPushNotificationsPermission()
|
||||
subscribeTangemPayOnWalletState()
|
||||
subscribeToMainScreenQrScanning()
|
||||
enableNotificationsIfNeeded()
|
||||
|
||||
clickIntents.initialize(innerWalletRouter, modelScope)
|
||||
|
|
@ -522,6 +541,7 @@ internal class WalletModel @Inject constructor(
|
|||
wallets = action.wallets,
|
||||
clickIntents = clickIntents,
|
||||
walletImageResolver = walletImageResolver,
|
||||
isMainScreenQrScanningEnabled = walletFeatureToggles.isMainScreenQrScanningEnabled,
|
||||
getWalletIconUseCase = getWalletIconUseCase,
|
||||
),
|
||||
)
|
||||
|
|
@ -727,6 +747,67 @@ internal class WalletModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun subscribeToMainScreenQrScanning() {
|
||||
listenToQrScanningUseCase.listen(SourceType.MAIN_SCREEN)
|
||||
.getOrElse { emptyFlow() }
|
||||
.onEach { rawResult -> handleQrResult(rawResult.qrCode, rawResult.resultSource) }
|
||||
.launchIn(modelScope)
|
||||
}
|
||||
|
||||
private suspend fun handleQrResult(qrCode: String, resultSource: QrResultSource) {
|
||||
when (val target = resolveQrSendTargetsUseCase(qrCode)) {
|
||||
is QrSendTarget.WalletConnect -> {
|
||||
val source = when (resultSource) {
|
||||
QrResultSource.CLIPBOARD -> WcPairRequest.Source.CLIPBOARD
|
||||
QrResultSource.CAMERA,
|
||||
QrResultSource.GALLERY,
|
||||
-> WcPairRequest.Source.QR
|
||||
}
|
||||
wcPairService.pair(
|
||||
WcPairRequest(
|
||||
userWalletId = stateHolder.getSelectedWalletId(),
|
||||
uri = target.uri,
|
||||
source = source,
|
||||
screen = WcPairRequest.Screen.MAIN,
|
||||
),
|
||||
)
|
||||
}
|
||||
is QrSendTarget.Single -> {
|
||||
innerWalletRouter.openSend(
|
||||
userWalletId = target.userWalletId,
|
||||
currency = target.currency,
|
||||
address = target.address,
|
||||
amount = target.amount?.parseBigDecimal(target.currency.decimals),
|
||||
tag = target.memo,
|
||||
entryType = AppRoute.Send.EntryType.QR,
|
||||
)
|
||||
}
|
||||
is QrSendTarget.Multiple -> {
|
||||
innerWalletRouter.openNetworkSelectionBottomSheet(target)
|
||||
}
|
||||
is QrSendTarget.Error -> handleQrError(target.error)
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleQrError(error: ClassifiedQrContent.Error) {
|
||||
when (error) {
|
||||
is ClassifiedQrContent.Error.Unrecognized -> {
|
||||
analyticsEventsHandler.send(
|
||||
WalletScreenAnalyticsEvent.MainScreen.NoticeUnrecognizedQr(),
|
||||
)
|
||||
uiMessageSender.send(WalletAlertUM.qrCodeUnrecognized())
|
||||
}
|
||||
is ClassifiedQrContent.Error.UnsupportedNetwork -> {
|
||||
analyticsEventsHandler.send(
|
||||
WalletScreenAnalyticsEvent.MainScreen.NoticeNoAvailableTokens(
|
||||
blockchain = error.blockchain,
|
||||
),
|
||||
)
|
||||
uiMessageSender.send(WalletAlertUM.qrCodeUnsupportedNetwork())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun enableNotificationsIfNeeded() {
|
||||
modelScope.launch {
|
||||
val isUserAllowToEnableNotifications = notificationsRepository.isUserAllowToSubscribeOnPushNotifications()
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ import com.tangem.domain.models.wallet.UserWallet
|
|||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.pay.TangemPayDetailsConfig
|
||||
import com.tangem.domain.pay.TangemPayEligibilityManager
|
||||
import com.tangem.domain.pay.model.TangemPayEntryPoint
|
||||
import com.tangem.domain.pay.repository.OnboardingRepository
|
||||
import com.tangem.domain.pay.usecase.ProduceTangemPayInitialDataUseCase
|
||||
import com.tangem.domain.pay.usecase.TangemPayMainScreenCustomerInfoUseCase
|
||||
|
|
@ -190,8 +191,8 @@ internal class TangemPayClickIntentsImplementor @Inject constructor(
|
|||
val issuingBottomSheet = bottomSheetMessage {
|
||||
infoBlock {
|
||||
icon(com.tangem.core.ui.R.drawable.ic_clock_24) {
|
||||
type = MessageBottomSheetUMV2.Icon.Type.Informative
|
||||
backgroundType = MessageBottomSheetUMV2.Icon.BackgroundType.Informative
|
||||
type = MessageBottomSheetUM.Icon.Type.Informative
|
||||
backgroundType = MessageBottomSheetUM.Icon.BackgroundType.Informative
|
||||
}
|
||||
title = resourceReference(R.string.tangempay_issuing_your_card)
|
||||
body = resourceReference(R.string.tangempay_issuing_your_card_description)
|
||||
|
|
@ -209,8 +210,8 @@ internal class TangemPayClickIntentsImplementor @Inject constructor(
|
|||
val issuingBottomSheet = bottomSheetMessage {
|
||||
infoBlock {
|
||||
icon(com.tangem.core.ui.R.drawable.ic_alert_24) {
|
||||
type = MessageBottomSheetUMV2.Icon.Type.Warning
|
||||
backgroundType = MessageBottomSheetUMV2.Icon.BackgroundType.Warning
|
||||
type = MessageBottomSheetUM.Icon.Type.Warning
|
||||
backgroundType = MessageBottomSheetUM.Icon.BackgroundType.Warning
|
||||
}
|
||||
title = resourceReference(R.string.tangempay_failed_to_issue_card)
|
||||
body = resourceReference(R.string.tangempay_failed_to_issue_card_support_description)
|
||||
|
|
@ -246,7 +247,7 @@ internal class TangemPayClickIntentsImplementor @Inject constructor(
|
|||
override fun onOnboardingBannerClick(userWalletId: UserWalletId) {
|
||||
modelScope.launch {
|
||||
analyticsEventHandler.send(TangemPayAnalyticsEvents.MainVisaPermanentBannerClicked())
|
||||
val isEligible = tangemPayEligibilityManager.getTangemPayAvailability()
|
||||
val isEligible = tangemPayEligibilityManager.getTangemPayAvailability(TangemPayEntryPoint.BANNER)
|
||||
if (isEligible) {
|
||||
router.openTangemPayOnboarding(mode = AppRoute.TangemPayOnboarding.Mode.FromBannerOnMain)
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package com.tangem.feature.wallet.child.wallet.model.intents
|
||||
|
||||
import com.tangem.core.decompose.di.ModelScoped
|
||||
import com.tangem.core.ui.DesignFeatureToggles
|
||||
import com.tangem.domain.exchange.RampStateManager
|
||||
import com.tangem.domain.models.wallet.isLocked
|
||||
import com.tangem.domain.onramp.FetchHotCryptoUseCase
|
||||
|
|
@ -15,6 +16,7 @@ import com.tangem.feature.wallet.presentation.wallet.domain.unwrap
|
|||
import com.tangem.feature.wallet.presentation.wallet.loaders.WalletScreenContentLoader
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletState
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletUM
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.transformers.SetRefreshStateTransformer
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.async
|
||||
|
|
@ -41,6 +43,7 @@ internal class WalletClickIntents @Inject constructor(
|
|||
private val onrampStatusFactory: OnrampStatusFactory,
|
||||
private val tangemPayIntents: TangemPayClickIntentsImplementor,
|
||||
private val yieldSupplyApyUpdateUseCase: YieldSupplyApyUpdateUseCase,
|
||||
private val designFeatureToggles: DesignFeatureToggles,
|
||||
) : BaseWalletClickIntents(),
|
||||
WalletCardClickIntents by walletCardClickIntentsImplementor,
|
||||
WalletWarningsClickIntents by warningsClickIntentsImplementer,
|
||||
|
|
@ -86,17 +89,24 @@ internal class WalletClickIntents @Inject constructor(
|
|||
}
|
||||
|
||||
fun onRefreshSwipe(showRefreshState: Boolean) {
|
||||
when (stateController.getSelectedWallet()) {
|
||||
is WalletState.MultiCurrency.Content -> {
|
||||
refreshMultiCurrencyContent(showRefreshState)
|
||||
if (designFeatureToggles.isRedesignEnabled) {
|
||||
when (stateController.getSelectedWalletUM()) {
|
||||
is WalletUM.Content -> refreshMultiCurrencyContent(showRefreshState)
|
||||
is WalletUM.Locked -> Unit
|
||||
}
|
||||
is WalletState.SingleCurrency.Content,
|
||||
-> {
|
||||
refreshSingleCurrencyContent(showRefreshState)
|
||||
} else {
|
||||
when (stateController.getSelectedWallet()) {
|
||||
is WalletState.MultiCurrency.Content -> {
|
||||
refreshMultiCurrencyContent(showRefreshState)
|
||||
}
|
||||
is WalletState.SingleCurrency.Content,
|
||||
-> {
|
||||
refreshSingleCurrencyContent(showRefreshState)
|
||||
}
|
||||
is WalletState.MultiCurrency.Locked,
|
||||
is WalletState.SingleCurrency.Locked,
|
||||
-> Unit
|
||||
}
|
||||
is WalletState.MultiCurrency.Locked,
|
||||
is WalletState.SingleCurrency.Locked,
|
||||
-> Unit
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import com.tangem.core.analytics.models.AnalyticsParam
|
|||
import com.tangem.core.analytics.models.event.MainScreenAnalyticsEvent
|
||||
import com.tangem.core.decompose.di.ModelScoped
|
||||
import com.tangem.core.decompose.ui.UiMessageSender
|
||||
import com.tangem.domain.account.status.supplier.SingleAccountStatusListSupplier
|
||||
import com.tangem.domain.models.account.Account
|
||||
import com.tangem.domain.models.account.AccountId
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
|
|
@ -18,7 +19,6 @@ import com.tangem.domain.models.wallet.UserWalletId
|
|||
import com.tangem.domain.nft.analytics.NFTAnalyticsEvent
|
||||
import com.tangem.domain.settings.ShouldShowMarketsTooltipUseCase
|
||||
import com.tangem.domain.tokens.GetCryptoCurrencyActionsUseCase
|
||||
import com.tangem.domain.tokens.GetSingleCryptoCurrencyStatusUseCase
|
||||
import com.tangem.domain.tokens.model.details.NavigationAction
|
||||
import com.tangem.domain.txhistory.usecase.GetExplorerTransactionUrlUseCase
|
||||
import com.tangem.domain.wallets.usecase.GetUserWalletUseCase
|
||||
|
|
@ -43,6 +43,7 @@ import kotlinx.coroutines.launch
|
|||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
@Suppress("TooManyFunctions")
|
||||
internal interface WalletContentClickIntents {
|
||||
|
||||
fun onDetailsClick()
|
||||
|
|
@ -69,6 +70,8 @@ internal interface WalletContentClickIntents {
|
|||
|
||||
fun onManageTokensClick(accountId: AccountId)
|
||||
|
||||
fun onManageTokensClick(userWalletId: UserWalletId)
|
||||
|
||||
fun onTransactionClick(txHash: String)
|
||||
|
||||
fun onDissmissBottomSheet()
|
||||
|
|
@ -82,16 +85,18 @@ internal interface WalletContentClickIntents {
|
|||
fun onDisposeExpressStatus()
|
||||
|
||||
fun onNFTClick(userWallet: UserWallet)
|
||||
|
||||
fun onScanQrClick()
|
||||
}
|
||||
|
||||
@Suppress("LongParameterList", "LargeClass")
|
||||
@Suppress("LongParameterList", "LargeClass", "TooManyFunctions")
|
||||
@ModelScoped
|
||||
internal class WalletContentClickIntentsImplementor @Inject constructor(
|
||||
private val stateHolder: WalletStateController,
|
||||
private val currencyActionsClickIntents: WalletCurrencyActionsClickIntentsImplementor,
|
||||
private val onrampStatusFactory: OnrampStatusFactory,
|
||||
private val getUserWalletUseCase: GetUserWalletUseCase,
|
||||
private val getSingleCryptoCurrencyStatusUseCase: GetSingleCryptoCurrencyStatusUseCase,
|
||||
private val singleAccountStatusListSupplier: SingleAccountStatusListSupplier,
|
||||
private val getCryptoCurrencyActionsUseCase: GetCryptoCurrencyActionsUseCase,
|
||||
private val getExplorerTransactionUrlUseCase: GetExplorerTransactionUrlUseCase,
|
||||
private val shouldShowMarketsTooltipUseCase: ShouldShowMarketsTooltipUseCase,
|
||||
|
|
@ -225,6 +230,12 @@ internal class WalletContentClickIntentsImplementor @Inject constructor(
|
|||
router.openManageTokensScreen(accountId)
|
||||
}
|
||||
|
||||
override fun onManageTokensClick(userWalletId: UserWalletId) {
|
||||
router.openManageTokensScreen(
|
||||
AccountId.forMainCryptoPortfolio(userWalletId),
|
||||
)
|
||||
}
|
||||
|
||||
private fun openYieldSupply(userWalletId: UserWalletId, cryptoCurrencyStatus: CryptoCurrencyStatus, apy: String) {
|
||||
router.openYieldSupplyEntryScreen(
|
||||
userWalletId = userWalletId,
|
||||
|
|
@ -266,7 +277,7 @@ internal class WalletContentClickIntentsImplementor @Inject constructor(
|
|||
|
||||
override fun onTransactionClick(txHash: String) {
|
||||
modelScope.launch(dispatchers.main) {
|
||||
val currency = getSingleCryptoCurrencyStatusUseCase.unwrap(
|
||||
val currency = singleAccountStatusListSupplier.unwrap(
|
||||
userWalletId = stateHolder.getSelectedWalletId(),
|
||||
)
|
||||
?.currency
|
||||
|
|
@ -369,4 +380,9 @@ internal class WalletContentClickIntentsImplementor @Inject constructor(
|
|||
|
||||
router.openNFT(userWallet)
|
||||
}
|
||||
|
||||
override fun onScanQrClick() {
|
||||
analyticsEventHandler.send(MainScreenAnalyticsEvent.ButtonQrScan())
|
||||
router.openQrScanner()
|
||||
}
|
||||
}
|
||||
|
|
@ -23,6 +23,7 @@ import com.tangem.core.ui.extensions.resourceReference
|
|||
import com.tangem.core.ui.haptic.TangemHapticEffect
|
||||
import com.tangem.core.ui.haptic.VibratorHapticManager
|
||||
import com.tangem.core.ui.message.DialogMessage
|
||||
import com.tangem.domain.account.status.supplier.SingleAccountStatusListSupplier
|
||||
import com.tangem.domain.account.status.usecase.ManageCryptoCurrenciesUseCase
|
||||
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
|
||||
import com.tangem.domain.appcurrency.extenstions.unwrap
|
||||
|
|
@ -43,7 +44,10 @@ import com.tangem.domain.onramp.model.OnrampSource
|
|||
import com.tangem.domain.promo.GetStoryContentUseCase
|
||||
import com.tangem.domain.promo.models.StoryContentIds
|
||||
import com.tangem.domain.staking.model.StakingOption
|
||||
import com.tangem.domain.tokens.*
|
||||
import com.tangem.domain.account.status.usecase.IsCryptoCurrencyCouldHideUseCase
|
||||
import com.tangem.domain.tokens.NeedShowYieldSupplyDepositedWarningUseCase
|
||||
import com.tangem.domain.tokens.SaveViewedTokenReceiveWarningUseCase
|
||||
import com.tangem.domain.tokens.SaveViewedYieldSupplyWarningUseCase
|
||||
import com.tangem.domain.tokens.model.ScenarioUnavailabilityReason
|
||||
import com.tangem.domain.tokens.model.analytics.TokenReceiveCopyActionSource
|
||||
import com.tangem.domain.tokens.model.analytics.TokenReceiveNewAnalyticsEvent
|
||||
|
|
@ -125,7 +129,7 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor(
|
|||
private val getSelectedWalletSyncUseCase: GetSelectedWalletSyncUseCase,
|
||||
private val walletManagersFacade: WalletManagersFacade,
|
||||
private val isDemoCardUseCase: IsDemoCardUseCase,
|
||||
private val getSingleCryptoCurrencyStatusUseCase: GetSingleCryptoCurrencyStatusUseCase,
|
||||
private val singleAccountStatusListSupplier: SingleAccountStatusListSupplier,
|
||||
private val getExploreUrlUseCase: GetExploreUrlUseCase,
|
||||
private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
|
||||
private val getStoryContentUseCase: GetStoryContentUseCase,
|
||||
|
|
@ -141,7 +145,7 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor(
|
|||
private val receiveAddressesFactory: ReceiveAddressesFactory,
|
||||
private val needShowYieldSupplyDepositedWarningUseCase: NeedShowYieldSupplyDepositedWarningUseCase,
|
||||
private val saveViewedYieldSupplyWarningUseCase: SaveViewedYieldSupplyWarningUseCase,
|
||||
private val isCryptoCurrencyCoinCouldHide: IsCryptoCurrencyCoinCouldHideUseCase,
|
||||
private val isCryptoCurrencyCouldHideUseCase: IsCryptoCurrencyCouldHideUseCase,
|
||||
private val manageCryptoCurrenciesUseCase: ManageCryptoCurrenciesUseCase,
|
||||
private val uiMessageSender: UiMessageSender,
|
||||
) : BaseWalletClickIntents(), WalletCurrencyActionsClickIntents {
|
||||
|
|
@ -264,17 +268,19 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor(
|
|||
|
||||
modelScope.launch(dispatchers.main) {
|
||||
val currency = cryptoCurrencyStatus.currency
|
||||
val isCryptoCurrencyCoinCouldHide = currency is CryptoCurrency.Coin &&
|
||||
!isCryptoCurrencyCoinCouldHide(userWalletId = accountId.userWalletId, cryptoCurrencyCoin = currency)
|
||||
val canHide = isCryptoCurrencyCouldHideUseCase(
|
||||
userWalletId = accountId.userWalletId,
|
||||
cryptoCurrency = currency,
|
||||
)
|
||||
|
||||
if (isCryptoCurrencyCoinCouldHide) {
|
||||
uiMessageSender.send(WalletAlertUM.unableHideToken(cryptoCurrency = cryptoCurrencyStatus.currency))
|
||||
} else {
|
||||
if (canHide) {
|
||||
uiMessageSender.send(
|
||||
WalletAlertUM.hideTokenConfirm(cryptoCurrency = cryptoCurrencyStatus.currency) {
|
||||
onPerformHideToken(accountId, cryptoCurrencyStatus)
|
||||
},
|
||||
)
|
||||
} else {
|
||||
uiMessageSender.send(WalletAlertUM.unableHideToken(cryptoCurrency = cryptoCurrencyStatus.currency))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -486,7 +492,7 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor(
|
|||
val userWalletId = stateHolder.getSelectedWalletId()
|
||||
|
||||
modelScope.launch(dispatchers.main) {
|
||||
val currencyStatus = getSingleCryptoCurrencyStatusUseCase.unwrap(userWalletId) ?: return@launch
|
||||
val currencyStatus = singleAccountStatusListSupplier.unwrap(userWalletId) ?: return@launch
|
||||
|
||||
when (val addresses = currencyStatus.value.networkAddress) {
|
||||
is NetworkAddress.Selectable -> {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.feature.wallet.featuretoggles
|
||||
|
||||
import com.tangem.core.configtoggle.FeatureToggles
|
||||
import com.tangem.core.configtoggle.feature.FeatureTogglesManager
|
||||
import com.tangem.features.wallet.featuretoggles.WalletFeatureToggles
|
||||
import javax.inject.Inject
|
||||
|
|
@ -9,5 +10,8 @@ internal class DefaultWalletFeatureToggles @Inject constructor(
|
|||
) : WalletFeatureToggles {
|
||||
|
||||
override val isWalletReorderFeatureEnabled: Boolean
|
||||
get() = featureToggles.isFeatureEnabled("WALLET_REORDER_FEATURE_ENABLED")
|
||||
get() = featureToggles.isFeatureEnabled(FeatureToggles.WALLET_REORDER_FEATURE_ENABLED)
|
||||
|
||||
override val isMainScreenQrScanningEnabled: Boolean
|
||||
get() = featureToggles.isFeatureEnabled(FeatureToggles.MAIN_SCREEN_QR_SCANNING_ENABLED)
|
||||
}
|
||||
|
|
@ -11,7 +11,7 @@ import kotlinx.collections.immutable.persistentListOf
|
|||
@Suppress("LargeClass")
|
||||
internal object WalletPreviewDataLegacy {
|
||||
|
||||
val topBarConfig by lazy { WalletTopBarConfig(onDetailsClick = {}) }
|
||||
val topBarConfig by lazy { WalletTopBarConfig() }
|
||||
|
||||
val walletCardContentState by lazy {
|
||||
WalletCardState.Content(
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@ package com.tangem.feature.wallet.presentation.common.preview
|
|||
|
||||
import com.tangem.core.ui.components.containers.pullToRefresh.PullToRefreshConfig
|
||||
import com.tangem.core.ui.components.currency.icon.CurrencyIconState
|
||||
import com.tangem.core.ui.ds.button.TangemButtonShape
|
||||
import com.tangem.core.ui.ds.button.TangemButtonSize
|
||||
import com.tangem.core.ui.ds.button.TangemButtonType
|
||||
import com.tangem.core.ui.ds.button.TangemButtonUM
|
||||
import com.tangem.core.ui.ds.image.TangemIconUM
|
||||
|
|
@ -40,6 +42,27 @@ internal object WalletScreenPreviewData {
|
|||
onItemLongClick = {},
|
||||
)
|
||||
|
||||
private val accountRowDefault = TangemTokenRowUM.Content(
|
||||
id = "1",
|
||||
headIconUM = TangemIconUM.Currency(CurrencyIconState.Loading),
|
||||
titleUM = TangemTokenRowUM.TitleUM.Content(
|
||||
text = stringReference("Main Account"),
|
||||
),
|
||||
subtitleUM = TangemTokenRowUM.SubtitleUM.Content(
|
||||
text = stringReference("2 tokens"),
|
||||
),
|
||||
topEndContentUM = TangemTokenRowUM.EndContentUM.Content(
|
||||
text = stringReference("1 234,56 \$"),
|
||||
),
|
||||
bottomEndContentUM = TangemTokenRowUM.EndContentUM.Content(
|
||||
text = stringReference("0,12345678 BTC"),
|
||||
),
|
||||
promoBannerUM = TangemTokenRowUM.PromoBannerUM.Empty,
|
||||
tailUM = TangemRowTailUM.Empty,
|
||||
onItemClick = {},
|
||||
onItemLongClick = {},
|
||||
)
|
||||
|
||||
private val tokenListDefault = WalletTokensListUM.Content(
|
||||
tokenList = persistentListOf(
|
||||
TokensListItemUM2.Token(tokenRowDefault.copy(id = "0")),
|
||||
|
|
@ -50,14 +73,79 @@ internal object WalletScreenPreviewData {
|
|||
text = resourceReference(R.string.organize_tokens_title),
|
||||
type = TangemButtonType.Secondary,
|
||||
onClick = {},
|
||||
iconRes = R.drawable.ic_filter_default_24,
|
||||
size = TangemButtonSize.X9,
|
||||
shape = TangemButtonShape.Rounded,
|
||||
),
|
||||
)
|
||||
|
||||
private val tokenListAccounts = WalletTokensListUM.Content(
|
||||
tokenList = persistentListOf(
|
||||
TokensListItemUM2.Portfolio(
|
||||
tokenRowUM = accountRowDefault.copy(id = "0"),
|
||||
tokenList = persistentListOf(
|
||||
TokensListItemUM2.Token(tokenRowDefault.copy(id = "1")),
|
||||
TokensListItemUM2.Token(tokenRowDefault.copy(id = "2")),
|
||||
),
|
||||
isExpanded = true,
|
||||
isCollapsable = true,
|
||||
onEmptyClick = {},
|
||||
),
|
||||
TokensListItemUM2.Portfolio(
|
||||
tokenRowUM = accountRowDefault.copy(id = "3"),
|
||||
tokenList = persistentListOf(
|
||||
TokensListItemUM2.Token(tokenRowDefault.copy(id = "4")),
|
||||
TokensListItemUM2.Token(tokenRowDefault.copy(id = "5")),
|
||||
),
|
||||
isExpanded = true,
|
||||
isCollapsable = true,
|
||||
onEmptyClick = {},
|
||||
),
|
||||
),
|
||||
organizeButtonUM = TangemButtonUM(
|
||||
text = resourceReference(R.string.organize_tokens_title),
|
||||
type = TangemButtonType.Secondary,
|
||||
onClick = {},
|
||||
iconRes = R.drawable.ic_filter_default_24,
|
||||
size = TangemButtonSize.X9,
|
||||
shape = TangemButtonShape.Rounded,
|
||||
),
|
||||
)
|
||||
|
||||
private val tokenListAccountsWithEmpty = WalletTokensListUM.Content(
|
||||
tokenList = persistentListOf(
|
||||
TokensListItemUM2.Portfolio(
|
||||
tokenRowUM = accountRowDefault.copy(id = "0"),
|
||||
tokenList = persistentListOf(),
|
||||
isExpanded = true,
|
||||
isCollapsable = true,
|
||||
onEmptyClick = {},
|
||||
),
|
||||
TokensListItemUM2.Portfolio(
|
||||
tokenRowUM = accountRowDefault.copy(id = "3"),
|
||||
tokenList = persistentListOf(),
|
||||
isExpanded = false,
|
||||
isCollapsable = true,
|
||||
onEmptyClick = {},
|
||||
),
|
||||
),
|
||||
organizeButtonUM = TangemButtonUM(
|
||||
text = resourceReference(R.string.organize_tokens_title),
|
||||
type = TangemButtonType.Secondary,
|
||||
onClick = {},
|
||||
iconRes = R.drawable.ic_filter_default_24,
|
||||
size = TangemButtonSize.X9,
|
||||
shape = TangemButtonShape.Rounded,
|
||||
),
|
||||
)
|
||||
|
||||
private val walletLocked = WalletUM.Locked(
|
||||
walletsBalanceUM = WalletBalancePreview.content,
|
||||
buttons = WalletPreviewData.actionButtons,
|
||||
walletsBalanceUM = WalletBalancePreview.empty,
|
||||
buttons = WalletPreviewData.disabledActionButtons,
|
||||
type = WalletType.Cold,
|
||||
notifications = persistentListOf(),
|
||||
notifications = persistentListOf(
|
||||
WalletNotificationUM.UnlockWallets({}),
|
||||
),
|
||||
)
|
||||
|
||||
private val walletDefault = WalletUM.Content(
|
||||
|
|
@ -82,13 +170,40 @@ internal object WalletScreenPreviewData {
|
|||
tangemPayState = TangemPayState.Loading,
|
||||
)
|
||||
|
||||
internal val defaultState = WalletScreenState(
|
||||
private val walletEmpty = WalletUM.Content(
|
||||
walletsBalanceUM = WalletBalancePreview.empty,
|
||||
buttons = WalletPreviewData.disabledActionButtons,
|
||||
type = WalletType.Cold,
|
||||
pullToRefreshConfig = PullToRefreshConfig(
|
||||
isRefreshing = false,
|
||||
onRefresh = {},
|
||||
),
|
||||
notifications = persistentListOf(),
|
||||
notificationsCarousel = persistentListOf(),
|
||||
tokensListUM = WalletTokensListUM.Empty(onEmptyClick = {}),
|
||||
nftState = WalletNFTItemUM.Hidden,
|
||||
tangemPayState = TangemPayState.Empty,
|
||||
)
|
||||
|
||||
private val walletAccountDefault = walletDefault.copy(
|
||||
tokensListUM = tokenListAccounts,
|
||||
)
|
||||
|
||||
private val walletAccountEmpty = walletEmpty.copy(
|
||||
walletsBalanceUM = WalletBalancePreview.content,
|
||||
tokensListUM = tokenListAccountsWithEmpty,
|
||||
)
|
||||
|
||||
val defaultState = WalletScreenState(
|
||||
topBarConfig = topBarConfig,
|
||||
selectedWalletIndex = 0,
|
||||
wallets = persistentListOf(),
|
||||
wallets2 = persistentListOf(
|
||||
walletLocked,
|
||||
walletDefault,
|
||||
walletEmpty,
|
||||
walletLocked,
|
||||
walletAccountDefault,
|
||||
walletAccountEmpty,
|
||||
),
|
||||
onWalletChange = { _, _ -> },
|
||||
event = consumedEvent(),
|
||||
|
|
@ -96,4 +211,20 @@ internal object WalletScreenPreviewData {
|
|||
showMarketsOnboarding = false,
|
||||
onDismissMarketsTooltip = {},
|
||||
)
|
||||
|
||||
val defaultAccountState = defaultState.copy(
|
||||
selectedWalletIndex = 3,
|
||||
)
|
||||
|
||||
val emptyState = defaultState.copy(
|
||||
selectedWalletIndex = 1,
|
||||
)
|
||||
|
||||
val emptyAccountState = defaultState.copy(
|
||||
selectedWalletIndex = 4,
|
||||
)
|
||||
|
||||
val lockedState = defaultState.copy(
|
||||
selectedWalletIndex = 2,
|
||||
)
|
||||
}
|
||||
|
|
@ -3,6 +3,7 @@ package com.tangem.feature.wallet.presentation.preview
|
|||
import androidx.compose.ui.text.SpanStyle
|
||||
import com.tangem.core.ui.ds.image.DeviceIconUM
|
||||
import com.tangem.core.ui.extensions.combinedReference
|
||||
import com.tangem.core.ui.extensions.orMaskWithStars
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.core.ui.extensions.styledStringReference
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
|
|
@ -37,6 +38,8 @@ internal object WalletBalancePreview {
|
|||
isZeroBalance = false,
|
||||
)
|
||||
|
||||
val hiddenBalanceContent = content.copy(balance = content.balance.orMaskWithStars(true))
|
||||
|
||||
val loading: WalletBalanceUM.Loading = WalletBalanceUM.Loading(
|
||||
id = UserWalletId("1"),
|
||||
name = "My Wallet",
|
||||
|
|
@ -48,4 +51,10 @@ internal object WalletBalancePreview {
|
|||
name = "My Wallet",
|
||||
deviceIcon = DeviceIconUM.Stub(cardsCount = 3),
|
||||
)
|
||||
|
||||
val empty: WalletBalanceUM.Empty = WalletBalanceUM.Empty(
|
||||
id = UserWalletId("2"),
|
||||
name = "My Wallet",
|
||||
deviceIcon = DeviceIconUM.Stub(cardsCount = 3),
|
||||
)
|
||||
}
|
||||
|
|
@ -15,6 +15,12 @@ internal object WalletPreviewData {
|
|||
}
|
||||
|
||||
val actionButtons = persistentListOf(
|
||||
WalletActionButtons.Buy({}, true).buttonUM,
|
||||
WalletActionButtons.Swap({}, true).buttonUM,
|
||||
WalletActionButtons.Sell({}, true).buttonUM,
|
||||
)
|
||||
|
||||
val disabledActionButtons = persistentListOf(
|
||||
WalletActionButtons.Buy({}, false).buttonUM,
|
||||
WalletActionButtons.Swap({}, false).buttonUM,
|
||||
WalletActionButtons.Sell({}, false).buttonUM,
|
||||
|
|
|
|||
|
|
@ -2,15 +2,18 @@ package com.tangem.feature.wallet.presentation.router
|
|||
|
||||
import com.arkivanov.decompose.router.slot.SlotNavigation
|
||||
import com.arkivanov.decompose.router.slot.activate
|
||||
import com.arkivanov.decompose.router.slot.dismiss
|
||||
import com.tangem.common.routing.AppRoute
|
||||
import com.tangem.common.routing.AppRouter
|
||||
import com.tangem.core.decompose.di.ModelScoped
|
||||
import com.tangem.core.navigation.url.UrlOpener
|
||||
import com.tangem.core.ui.DesignFeatureToggles
|
||||
import com.tangem.domain.models.TokenReceiveConfig
|
||||
import com.tangem.domain.models.account.AccountId
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.qrscanning.models.QrSendTarget
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.pay.TangemPayDetailsConfig
|
||||
|
|
@ -18,6 +21,7 @@ import com.tangem.domain.redux.ReduxStateHolder
|
|||
import com.tangem.domain.redux.StateDialog
|
||||
import com.tangem.domain.tokens.model.details.NavigationAction
|
||||
import com.tangem.domain.tokens.model.details.TokenAction
|
||||
import com.tangem.feature.wallet.child.organizetokens.OrganizeTokensComponent
|
||||
import com.tangem.feature.wallet.navigation.WalletRoute
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.model.TokenActionButtonUM
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletDialogConfig
|
||||
|
|
@ -32,6 +36,7 @@ internal class DefaultWalletRouter @Inject constructor(
|
|||
private val router: AppRouter,
|
||||
private val urlOpener: UrlOpener,
|
||||
private val reduxStateHolder: ReduxStateHolder,
|
||||
private val designFeatureToggles: DesignFeatureToggles,
|
||||
) : InnerWalletRouter {
|
||||
|
||||
override val dialogNavigation: SlotNavigation<WalletDialogConfig> = SlotNavigation()
|
||||
|
|
@ -41,8 +46,17 @@ internal class DefaultWalletRouter @Inject constructor(
|
|||
onBufferOverflow = BufferOverflow.DROP_LATEST,
|
||||
)
|
||||
|
||||
override val organizeCallbacks: OrganizeTokensComponent.Callback
|
||||
get() = OrganizeCallbacks()
|
||||
|
||||
override fun openOrganizeTokensScreen(userWalletId: UserWalletId) {
|
||||
navigateToFlow.tryEmit(WalletRoute.OrganizeTokens(userWalletId))
|
||||
if (designFeatureToggles.isRedesignEnabled) {
|
||||
dialogNavigation.activate(
|
||||
configuration = WalletDialogConfig.OrganizeTokens(userWalletId),
|
||||
)
|
||||
} else {
|
||||
navigateToFlow.tryEmit(WalletRoute.OrganizeTokens(userWalletId))
|
||||
}
|
||||
}
|
||||
|
||||
override fun openDetailsScreen(selectedWalletId: UserWalletId) {
|
||||
|
|
@ -161,4 +175,58 @@ internal class DefaultWalletRouter @Inject constructor(
|
|||
),
|
||||
)
|
||||
}
|
||||
|
||||
override fun openQrScanner() {
|
||||
router.push(AppRoute.QrScanning(source = AppRoute.QrScanning.Source.MainScreen))
|
||||
}
|
||||
|
||||
override fun openSend(
|
||||
userWalletId: UserWalletId,
|
||||
currency: CryptoCurrency,
|
||||
address: String,
|
||||
amount: String?,
|
||||
tag: String?,
|
||||
entryType: AppRoute.Send.EntryType,
|
||||
) {
|
||||
router.push(
|
||||
AppRoute.Send(
|
||||
userWalletId = userWalletId,
|
||||
currency = currency,
|
||||
destinationAddress = address,
|
||||
amount = amount,
|
||||
tag = tag,
|
||||
entryType = entryType,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
override fun openNetworkSelectionBottomSheet(target: QrSendTarget.Multiple) {
|
||||
dialogNavigation.activate(
|
||||
configuration = WalletDialogConfig.NetworkSelection(
|
||||
address = target.address,
|
||||
amount = target.amount,
|
||||
memo = target.memo,
|
||||
walletGroups = target.walletGroups.map { walletGroup ->
|
||||
WalletDialogConfig.NetworkSelection.WalletGroupData(
|
||||
userWalletId = walletGroup.userWalletId,
|
||||
walletName = walletGroup.walletName,
|
||||
accounts = walletGroup.accounts.map { accountGroup ->
|
||||
WalletDialogConfig.NetworkSelection.AccountGroupData(
|
||||
accountId = accountGroup.accountId,
|
||||
accountName = accountGroup.accountName,
|
||||
currencies = accountGroup.currencies,
|
||||
hiddenTokensCount = accountGroup.hiddenTokensCount,
|
||||
)
|
||||
},
|
||||
)
|
||||
},
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
inner class OrganizeCallbacks : OrganizeTokensComponent.Callback {
|
||||
override fun onDismiss() {
|
||||
dialogNavigation.dismiss()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -8,11 +8,13 @@ import com.tangem.domain.models.account.AccountId
|
|||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.qrscanning.models.QrSendTarget
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.pay.TangemPayDetailsConfig
|
||||
import com.tangem.domain.tokens.model.details.NavigationAction
|
||||
import com.tangem.domain.tokens.model.details.TokenAction
|
||||
import com.tangem.feature.wallet.child.organizetokens.OrganizeTokensComponent
|
||||
import com.tangem.feature.wallet.navigation.WalletRoute
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.model.TokenActionButtonUM
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletDialogConfig
|
||||
|
|
@ -34,6 +36,8 @@ internal interface InnerWalletRouter {
|
|||
|
||||
val navigateToFlow: SharedFlow<WalletRoute>
|
||||
|
||||
val organizeCallbacks: OrganizeTokensComponent.Callback
|
||||
|
||||
/** Open organize tokens screen */
|
||||
fun openOrganizeTokensScreen(userWalletId: UserWalletId)
|
||||
|
||||
|
|
@ -86,4 +90,20 @@ internal interface InnerWalletRouter {
|
|||
|
||||
/** Open token action sheet */
|
||||
fun openTokenActionSheet(userWallet: UserWallet, tokenActionList: ImmutableList<TokenActionButtonUM>)
|
||||
|
||||
/** Open QR scanner screen */
|
||||
fun openQrScanner()
|
||||
|
||||
/** Open send screen with prefilled destination */
|
||||
fun openSend(
|
||||
userWalletId: UserWalletId,
|
||||
currency: CryptoCurrency,
|
||||
address: String,
|
||||
amount: String?,
|
||||
tag: String?,
|
||||
entryType: AppRoute.Send.EntryType = AppRoute.Send.EntryType.Manual,
|
||||
)
|
||||
|
||||
/** Open network selection bottom sheet for multiple QR matches */
|
||||
fun openNetworkSelectionBottomSheet(target: QrSendTarget.Multiple)
|
||||
}
|
||||
|
|
@ -167,6 +167,17 @@ sealed class WalletScreenAnalyticsEvent {
|
|||
|
||||
class NoticeSeedPhraseSupportButtonDeclined : MainScreen(event = "Button - Support Declined")
|
||||
|
||||
class NoticeUnrecognizedQr : MainScreen(
|
||||
event = "Notice - Unrecognized QR",
|
||||
)
|
||||
|
||||
class NoticeNoAvailableTokens(blockchain: String?) : MainScreen(
|
||||
event = "Notice - No Available Tokens",
|
||||
params = buildMap {
|
||||
if (blockchain != null) put("Blockchain", blockchain)
|
||||
},
|
||||
)
|
||||
|
||||
// region Referral Promo
|
||||
class ReferralPromo : MainScreen(event = "Referral Banner")
|
||||
class ReferralPromoButtonParticipate : MainScreen(event = "Button - Referral Participate")
|
||||
|
|
|
|||
|
|
@ -122,6 +122,7 @@ internal class WalletWarningsAnalyticsSender @Inject constructor(
|
|||
is WalletNotification.Warning.TangemPayRefreshNeeded -> null
|
||||
is WalletNotification.Warning.TangemPayUnreachable -> null
|
||||
is WalletNotification.UpgradeHotWalletPromo -> null
|
||||
is WalletNotification.TokenSyncCompleted -> null
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -114,8 +114,8 @@ internal class WalletWarningsSingleEventSender @Inject constructor(
|
|||
val message = bottomSheetMessage {
|
||||
infoBlock {
|
||||
icon(R.drawable.img_knight_shield_32) {
|
||||
type = MessageBottomSheetUMV2.Icon.Type.Warning
|
||||
backgroundType = MessageBottomSheetUMV2.Icon.BackgroundType.SameAsTint
|
||||
type = MessageBottomSheetUM.Icon.Type.Warning
|
||||
backgroundType = MessageBottomSheetUM.Icon.BackgroundType.SameAsTint
|
||||
}
|
||||
title = resourceReference(R.string.hw_activation_need_title)
|
||||
body = resourceReference(R.string.hw_activation_need_description)
|
||||
|
|
|
|||
|
|
@ -1,13 +1,11 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.domain
|
||||
|
||||
import arrow.core.Either
|
||||
import com.tangem.domain.account.status.producer.SingleAccountStatusListProducer
|
||||
import com.tangem.domain.account.status.supplier.SingleAccountStatusListSupplier
|
||||
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.tokens.GetSingleCryptoCurrencyStatusUseCase
|
||||
import com.tangem.domain.tokens.error.CurrencyStatusError
|
||||
import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase
|
||||
import kotlinx.coroutines.flow.*
|
||||
import timber.log.Timber
|
||||
|
||||
internal fun GetSelectedWalletSyncUseCase.unwrap(): UserWallet? {
|
||||
|
|
@ -20,29 +18,9 @@ internal fun GetSelectedWalletSyncUseCase.unwrap(): UserWallet? {
|
|||
)
|
||||
}
|
||||
|
||||
internal suspend fun GetSingleCryptoCurrencyStatusUseCase.unwrap(userWalletId: UserWalletId): CryptoCurrencyStatus? {
|
||||
return invokeSingleWallet(userWalletId)
|
||||
.conflate()
|
||||
.distinctUntilChanged()
|
||||
.filter(Either<CurrencyStatusError, CryptoCurrencyStatus>::isRight)
|
||||
.firstOrNull()
|
||||
?.fold(
|
||||
ifLeft = {
|
||||
Timber.e("Impossible to get primary currency status $it")
|
||||
null
|
||||
},
|
||||
ifRight = { it },
|
||||
)
|
||||
}
|
||||
|
||||
internal suspend fun GetSingleCryptoCurrencyStatusUseCase.collectLatest(
|
||||
userWalletId: UserWalletId,
|
||||
onRight: suspend (CryptoCurrencyStatus) -> Unit,
|
||||
) {
|
||||
invokeSingleWallet(userWalletId = userWalletId)
|
||||
.conflate()
|
||||
.distinctUntilChanged()
|
||||
.collectLatest { maybeStatus ->
|
||||
maybeStatus.onRight { onRight(it) }
|
||||
}
|
||||
internal suspend fun SingleAccountStatusListSupplier.unwrap(userWalletId: UserWalletId): CryptoCurrencyStatus? {
|
||||
return getSyncOrNull(params = SingleAccountStatusListProducer.Params(userWalletId))
|
||||
?.mainAccount
|
||||
?.flattenCurrencies()
|
||||
?.firstOrNull()
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.qr
|
||||
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import java.math.BigDecimal
|
||||
|
||||
internal sealed class ClassifiedQrContent {
|
||||
|
||||
data class WalletConnect(val uri: String) : ClassifiedQrContent()
|
||||
|
||||
data class PaymentUri(
|
||||
val currency: CryptoCurrency,
|
||||
val address: String,
|
||||
val amount: BigDecimal?,
|
||||
val memo: String?,
|
||||
) : ClassifiedQrContent()
|
||||
|
||||
data class PlainAddress(
|
||||
val address: String,
|
||||
val matchingCurrencies: List<CryptoCurrency>,
|
||||
) : ClassifiedQrContent()
|
||||
|
||||
data class Unknown(val raw: String) : ClassifiedQrContent()
|
||||
}
|
||||
|
|
@ -0,0 +1,120 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.qr
|
||||
|
||||
import com.tangem.blockchainsdk.utils.toBlockchain
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.network.Network
|
||||
import java.net.URLDecoder
|
||||
|
||||
internal class QrContentClassifier(
|
||||
private val blockchainDataProvider: BlockchainDataProvider,
|
||||
) {
|
||||
|
||||
fun classify(qrCode: String, userCurrencies: List<CryptoCurrency>): ClassifiedQrContent {
|
||||
if (qrCode.startsWith(WC_PREFIX)) {
|
||||
return ClassifiedQrContent.WalletConnect(qrCode)
|
||||
}
|
||||
|
||||
if (isDAppWcUrl(qrCode)) {
|
||||
return ClassifiedQrContent.WalletConnect(qrCode)
|
||||
}
|
||||
|
||||
val coins = userCurrencies.filterIsInstance<CryptoCurrency.Coin>()
|
||||
val uniqueCoins = coins.distinctBy { it.network.id }
|
||||
|
||||
val paymentUri = tryParsePaymentUri(qrCode, uniqueCoins)
|
||||
if (paymentUri != null) return paymentUri
|
||||
|
||||
val matchingCurrencies = uniqueCoins.filter { coin ->
|
||||
blockchainDataProvider.validateAddress(coin.network, qrCode)
|
||||
}
|
||||
|
||||
if (matchingCurrencies.isNotEmpty()) {
|
||||
return ClassifiedQrContent.PlainAddress(
|
||||
address = qrCode,
|
||||
matchingCurrencies = matchingCurrencies,
|
||||
)
|
||||
}
|
||||
|
||||
return ClassifiedQrContent.Unknown(qrCode)
|
||||
}
|
||||
|
||||
private fun tryParsePaymentUri(qrCode: String, coins: List<CryptoCurrency.Coin>): ClassifiedQrContent.PaymentUri? {
|
||||
return coins.firstNotNullOfOrNull { coin ->
|
||||
val matchedScheme = blockchainDataProvider.getShareSchemes(coin.network)
|
||||
.sortedByDescending { it.length }
|
||||
.firstOrNull { qrCode.startsWith(it) }
|
||||
?: return@firstNotNullOfOrNull null
|
||||
|
||||
val withoutScheme = qrCode.removePrefix(matchedScheme)
|
||||
val address = withoutScheme.takeWhile {
|
||||
it != CHAIN_DELIMITER && it != FUNCTION_DELIMITER && it != PARAM_DELIMITER
|
||||
}
|
||||
val params = extractParameters(withoutScheme)
|
||||
|
||||
if (address.isBlank()) return@firstNotNullOfOrNull null
|
||||
|
||||
val amount = params[PARAM_AMOUNT]?.toBigDecimalOrNull()
|
||||
val memo = (params[PARAM_MEMO] ?: params[PARAM_MESSAGE])?.let {
|
||||
runCatching { URLDecoder.decode(it, CHARSET_UTF8) }.getOrDefault(it)
|
||||
}
|
||||
|
||||
ClassifiedQrContent.PaymentUri(
|
||||
currency = coin,
|
||||
address = address,
|
||||
amount = amount,
|
||||
memo = memo,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun isDAppWcUrl(qrCode: String): Boolean {
|
||||
if (!qrCode.startsWith(HTTP_PREFIX) && !qrCode.startsWith(HTTPS_PREFIX)) return false
|
||||
|
||||
val uriParam = extractParameters(qrCode)[PARAM_URI] ?: return false
|
||||
val decodedUri = runCatching { URLDecoder.decode(uriParam, CHARSET_UTF8) }.getOrDefault(uriParam)
|
||||
return decodedUri.startsWith(WC_PREFIX)
|
||||
}
|
||||
|
||||
private fun extractParameters(from: String): Map<String, String> {
|
||||
val paramsBlock = from.substringAfter(PARAM_DELIMITER, missingDelimiterValue = "")
|
||||
if (paramsBlock.isBlank()) return emptyMap()
|
||||
|
||||
return paramsBlock.split(PARAMS_DELIMITER)
|
||||
.mapNotNull { param ->
|
||||
val parts = param.split(PARAM_VALUE_DELIMITER, limit = 2)
|
||||
if (parts.size == 2) parts[0].lowercase() to parts[1] else null
|
||||
}
|
||||
.toMap()
|
||||
}
|
||||
|
||||
internal interface BlockchainDataProvider {
|
||||
fun getShareSchemes(network: Network): List<String>
|
||||
fun validateAddress(network: Network, address: String): Boolean
|
||||
}
|
||||
|
||||
internal class DefaultBlockchainDataProvider : BlockchainDataProvider {
|
||||
override fun getShareSchemes(network: Network): List<String> {
|
||||
return runCatching { network.toBlockchain().getShareScheme() }.getOrDefault(emptyList())
|
||||
}
|
||||
|
||||
override fun validateAddress(network: Network, address: String): Boolean {
|
||||
return runCatching { network.toBlockchain().validateAddress(address) }.getOrDefault(false)
|
||||
}
|
||||
}
|
||||
|
||||
private companion object {
|
||||
const val HTTP_PREFIX = "http://"
|
||||
const val HTTPS_PREFIX = "https://"
|
||||
const val PARAM_URI = "uri"
|
||||
const val WC_PREFIX = "wc:"
|
||||
const val CHAIN_DELIMITER = '@'
|
||||
const val FUNCTION_DELIMITER = '/'
|
||||
const val PARAM_DELIMITER = '?'
|
||||
const val PARAMS_DELIMITER = '&'
|
||||
const val PARAM_VALUE_DELIMITER = '='
|
||||
const val PARAM_AMOUNT = "amount"
|
||||
const val PARAM_MEMO = "memo"
|
||||
const val PARAM_MESSAGE = "message"
|
||||
const val CHARSET_UTF8 = "UTF-8"
|
||||
}
|
||||
}
|
||||
|
|
@ -130,7 +130,7 @@ internal class WalletStateController @Inject constructor(
|
|||
|
||||
private fun getInitialState(): WalletScreenState {
|
||||
return WalletScreenState(
|
||||
topBarConfig = WalletTopBarConfig(onDetailsClick = {}),
|
||||
topBarConfig = WalletTopBarConfig(),
|
||||
selectedWalletIndex = NOT_INITIALIZED_WALLET_INDEX,
|
||||
wallets = persistentListOf(),
|
||||
wallets2 = persistentListOf(),
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ package com.tangem.feature.wallet.presentation.wallet.state.model
|
|||
import androidx.annotation.DrawableRes
|
||||
import androidx.compose.runtime.Immutable
|
||||
import com.tangem.core.ui.ds.button.TangemButtonShape
|
||||
import com.tangem.core.ui.ds.button.TangemButtonState
|
||||
import com.tangem.core.ui.ds.button.TangemButtonType
|
||||
import com.tangem.core.ui.ds.button.TangemButtonUM
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
|
|
@ -31,11 +30,6 @@ internal sealed class WalletActionButtons(
|
|||
shape = TangemButtonShape.Rounded,
|
||||
onClick = onClick,
|
||||
isEnabled = isEnabled,
|
||||
state = if (isEnabled) {
|
||||
TangemButtonState.Default
|
||||
} else {
|
||||
TangemButtonState.Disabled
|
||||
},
|
||||
)
|
||||
|
||||
data class Buy(
|
||||
|
|
|
|||
|
|
@ -8,4 +8,5 @@ data class WalletAdditionalInfo(
|
|||
val hideable: Boolean,
|
||||
val content: TextReference,
|
||||
val isHotBackedUp: Boolean = false,
|
||||
val shouldShowProgress: Boolean = false,
|
||||
)
|
||||
|
|
@ -80,6 +80,20 @@ internal object WalletAlertUM {
|
|||
)
|
||||
}
|
||||
|
||||
fun qrCodeUnrecognized(): DialogMessage {
|
||||
return DialogMessage(
|
||||
title = resourceReference(R.string.qr_scanner_error_unrecognized_title),
|
||||
message = resourceReference(R.string.qr_scanner_error_unrecognized_message),
|
||||
)
|
||||
}
|
||||
|
||||
fun qrCodeUnsupportedNetwork(): DialogMessage {
|
||||
return DialogMessage(
|
||||
title = resourceReference(R.string.qr_scanner_error_token_not_added_title),
|
||||
message = resourceReference(R.string.qr_scanner_error_token_not_added_message),
|
||||
)
|
||||
}
|
||||
|
||||
fun confirmExpressStatusHide(onConfirmClick: () -> Unit): DialogMessage {
|
||||
return DialogMessage(
|
||||
title = resourceReference(R.string.express_status_hide_dialog_title),
|
||||
|
|
|
|||
|
|
@ -69,11 +69,24 @@ internal sealed interface WalletBalanceUM {
|
|||
override val deviceIcon: DeviceIconUM,
|
||||
) : WalletBalanceUM
|
||||
|
||||
/**
|
||||
* Wallet card loading state
|
||||
*
|
||||
* @property id wallet id
|
||||
* @property name wallet name
|
||||
*/
|
||||
data class Empty(
|
||||
override val id: UserWalletId,
|
||||
override val name: String,
|
||||
override val deviceIcon: DeviceIconUM,
|
||||
) : WalletBalanceUM
|
||||
|
||||
fun copySealed(name: String): WalletBalanceUM {
|
||||
return when (this) {
|
||||
is Content -> copy(name = name)
|
||||
is Error -> copy(name = name)
|
||||
is Loading -> copy(name = name)
|
||||
is Empty -> copy(name = name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,11 +1,15 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.state.model
|
||||
|
||||
import com.tangem.domain.models.TokenReceiveConfig
|
||||
import com.tangem.domain.models.account.AccountId
|
||||
import com.tangem.domain.models.account.AccountName
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.serialization.BigDecimalSerializer
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.tokens.model.details.TokenAction
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.serialization.Serializable
|
||||
import java.math.BigDecimal
|
||||
|
||||
/**
|
||||
* Wallet dialog config. Used to show Decompose dialogs
|
||||
|
|
@ -41,4 +45,31 @@ internal sealed interface WalletDialogConfig {
|
|||
|
||||
@Serializable
|
||||
data class KycRejected(val walletId: UserWalletId, val customerId: String) : WalletDialogConfig
|
||||
|
||||
@Serializable
|
||||
data class OrganizeTokens(val userWalletId: UserWalletId) : WalletDialogConfig
|
||||
|
||||
@Serializable
|
||||
data class NetworkSelection(
|
||||
val address: String,
|
||||
val amount: @Serializable(BigDecimalSerializer::class) BigDecimal?,
|
||||
val memo: String?,
|
||||
val walletGroups: List<WalletGroupData>,
|
||||
) : WalletDialogConfig {
|
||||
|
||||
@Serializable
|
||||
data class WalletGroupData(
|
||||
val userWalletId: UserWalletId,
|
||||
val walletName: String,
|
||||
val accounts: List<AccountGroupData>,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class AccountGroupData(
|
||||
val accountId: AccountId,
|
||||
val accountName: AccountName,
|
||||
val currencies: List<CryptoCurrency>,
|
||||
val hiddenTokensCount: Int = 0,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -463,4 +463,21 @@ sealed class WalletNotification(val config: NotificationConfig) {
|
|||
iconSize = 72.dp,
|
||||
),
|
||||
)
|
||||
|
||||
data class TokenSyncCompleted(
|
||||
val onCloseClick: () -> Unit,
|
||||
val onManageTokensClick: () -> Unit,
|
||||
) : WalletNotification(
|
||||
config = NotificationConfig(
|
||||
title = resourceReference(R.string.initial_wallet_sync_banner_title),
|
||||
subtitle = resourceReference(R.string.initial_wallet_sync_banner_description),
|
||||
iconResId = R.drawable.ic_check_circle_24,
|
||||
iconTint = IconTint.Accent,
|
||||
onCloseClick = onCloseClick,
|
||||
buttonsState = ButtonsState.SecondaryButtonConfig(
|
||||
text = resourceReference(R.string.main_manage_tokens),
|
||||
onClick = onManageTokensClick,
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.state.model
|
||||
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.tangem.core.ui.ds.button.TangemButtonType
|
||||
import com.tangem.core.ui.ds.image.TangemIconUM
|
||||
import com.tangem.core.ui.ds.message.TangemMessageButtonUM
|
||||
|
|
@ -196,7 +197,6 @@ internal sealed class WalletNotificationUM(val messageUM: TangemMessageUM, val t
|
|||
onClick = onConfirmClick,
|
||||
),
|
||||
),
|
||||
|
||||
),
|
||||
type = WalletNotificationType.Critical,
|
||||
)
|
||||
|
|
@ -343,7 +343,13 @@ internal sealed class WalletNotificationUM(val messageUM: TangemMessageUM, val t
|
|||
resourceReference(R.string.common_biometrics),
|
||||
),
|
||||
),
|
||||
onClick = onClick,
|
||||
buttonsUM = persistentListOf(
|
||||
TangemMessageButtonUM(
|
||||
text = resourceReference(R.string.common_unlock),
|
||||
onClick = onClick,
|
||||
type = TangemButtonType.Primary,
|
||||
),
|
||||
),
|
||||
messageEffect = TangemMessageEffect.Card,
|
||||
isCentered = true,
|
||||
),
|
||||
|
|
@ -379,8 +385,14 @@ internal sealed class WalletNotificationUM(val messageUM: TangemMessageUM, val t
|
|||
title = resourceReference(R.string.notification_one_plus_one_title),
|
||||
subtitle = resourceReference(R.string.notification_one_plus_one_text),
|
||||
messageEffect = TangemMessageEffect.Magic,
|
||||
onCloseClick = onCloseClick,
|
||||
iconUM = TangemIconUM.Image(R.drawable.img_one_plus_one_promo),
|
||||
iconSize = 54.dp,
|
||||
buttonsUM = persistentListOf(
|
||||
TangemMessageButtonUM(
|
||||
text = resourceReference(R.string.common_later),
|
||||
type = TangemButtonType.PrimaryInverse,
|
||||
onClick = onCloseClick,
|
||||
),
|
||||
TangemMessageButtonUM(
|
||||
text = resourceReference(R.string.notification_one_plus_one_button),
|
||||
type = TangemButtonType.Primary,
|
||||
|
|
@ -452,7 +464,8 @@ internal sealed class WalletNotificationUM(val messageUM: TangemMessageUM, val t
|
|||
id = "PushNotificationsNotification",
|
||||
title = resourceReference(R.string.user_push_notification_banner_title),
|
||||
subtitle = resourceReference(R.string.user_push_notification_banner_subtitle),
|
||||
onCloseClick = onCloseClick,
|
||||
iconUM = TangemIconUM.Image(R.drawable.img_push_reminder),
|
||||
iconSize = 54.dp,
|
||||
messageEffect = TangemMessageEffect.Magic,
|
||||
buttonsUM = persistentListOf(
|
||||
TangemMessageButtonUM(
|
||||
|
|
|
|||
|
|
@ -20,11 +20,33 @@ internal sealed class WalletTokensListUM {
|
|||
abstract val tokenList: ImmutableList<TokensListItemUM2>
|
||||
abstract val organizeButtonUM: TangemButtonUM?
|
||||
|
||||
data object Empty : WalletTokensListUM() {
|
||||
data class Empty(
|
||||
val onEmptyClick: () -> Unit,
|
||||
) : WalletTokensListUM() {
|
||||
override val tokenList: ImmutableList<TokensListItemUM2.Portfolio> = persistentListOf()
|
||||
override val organizeButtonUM: TangemButtonUM? = null
|
||||
}
|
||||
|
||||
data object Locked : WalletTokensListUM() {
|
||||
override val tokenList: ImmutableList<TokensListItemUM2.Portfolio> = persistentListOf(
|
||||
TokensListItemUM2.Portfolio(
|
||||
tokenRowUM = TangemTokenRowUM.Empty(id = "0"),
|
||||
tokenList = persistentListOf(),
|
||||
isExpanded = false,
|
||||
isCollapsable = true,
|
||||
onEmptyClick = {},
|
||||
),
|
||||
TokensListItemUM2.Portfolio(
|
||||
tokenRowUM = TangemTokenRowUM.Empty(id = "1"),
|
||||
tokenList = persistentListOf(),
|
||||
isExpanded = false,
|
||||
isCollapsable = true,
|
||||
onEmptyClick = {},
|
||||
),
|
||||
)
|
||||
override val organizeButtonUM: TangemButtonUM? = null
|
||||
}
|
||||
|
||||
data object Loading : WalletTokensListUM() {
|
||||
override val tokenList: ImmutableList<TokensListItemUM2> = persistentListOf(
|
||||
TokensListItemUM2.Portfolio(
|
||||
|
|
@ -32,18 +54,21 @@ internal sealed class WalletTokensListUM {
|
|||
tokenList = persistentListOf(),
|
||||
isExpanded = false,
|
||||
isCollapsable = true,
|
||||
onEmptyClick = {},
|
||||
),
|
||||
TokensListItemUM2.Portfolio(
|
||||
tokenRowUM = TangemTokenRowUM.Loading(id = "1"),
|
||||
tokenList = persistentListOf(),
|
||||
isExpanded = false,
|
||||
isCollapsable = true,
|
||||
onEmptyClick = {},
|
||||
),
|
||||
TokensListItemUM2.Portfolio(
|
||||
tokenRowUM = TangemTokenRowUM.Loading(id = "2"),
|
||||
tokenList = persistentListOf(),
|
||||
isExpanded = false,
|
||||
isCollapsable = true,
|
||||
onEmptyClick = {},
|
||||
),
|
||||
)
|
||||
override val organizeButtonUM: TangemButtonUM? = null
|
||||
|
|
@ -72,6 +97,7 @@ internal sealed interface TokensListItemUM2 {
|
|||
|
||||
data class Portfolio(
|
||||
override val tokenRowUM: TangemTokenRowUM,
|
||||
val onEmptyClick: () -> Unit,
|
||||
val tokenList: ImmutableList<TokensListItemUM2>,
|
||||
val isExpanded: Boolean,
|
||||
val isCollapsable: Boolean,
|
||||
|
|
|
|||
|
|
@ -1,8 +1,16 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.state.model
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import com.tangem.core.ui.ds.topbar.TangemTopBarActionUM
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
|
||||
/**
|
||||
* Wallet screen top bar config
|
||||
*
|
||||
* @property onDetailsClick lambda be invoked when details button is clicked
|
||||
* @property endActions list of top bar end action buttons (e.g. QR scan, More)
|
||||
*/
|
||||
internal data class WalletTopBarConfig(val onDetailsClick: () -> Unit)
|
||||
@Immutable
|
||||
internal data class WalletTopBarConfig(
|
||||
val endActions: ImmutableList<TangemTopBarActionUM> = persistentListOf(),
|
||||
)
|
||||
|
|
@ -45,7 +45,7 @@ internal sealed interface WalletUM {
|
|||
) : WalletUM {
|
||||
override val notificationsCarousel: ImmutableList<WalletNotificationUM> = persistentListOf()
|
||||
override val pullToRefreshConfig = PullToRefreshConfig(false, {})
|
||||
override val tokensListUM: WalletTokensListUM = WalletTokensListUM.Empty // todo redesign main locked state
|
||||
override val tokensListUM: WalletTokensListUM = WalletTokensListUM.Locked
|
||||
override val nftState: WalletNFTItemUM = WalletNFTItemUM.Hidden
|
||||
override val tangemPayState: TangemPayState = TangemPayState.Empty
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package com.tangem.feature.wallet.presentation.wallet.state.transformers
|
|||
|
||||
import com.tangem.common.ui.userwallet.converter.WalletIconUMConverter
|
||||
import com.tangem.core.ui.ds.button.TangemButtonUM
|
||||
import com.tangem.core.ui.ds.topbar.TangemTopBarActionUM
|
||||
import com.tangem.domain.card.common.util.cardTypesResolver
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.models.wallet.isLocked
|
||||
|
|
@ -18,6 +19,7 @@ import kotlinx.collections.immutable.PersistentList
|
|||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlinx.collections.immutable.toPersistentList
|
||||
import com.tangem.core.ui.R as CoreUiR
|
||||
|
||||
internal class InitializeWalletsTransformer(
|
||||
private val selectedWalletIndex: Int,
|
||||
|
|
@ -25,6 +27,7 @@ internal class InitializeWalletsTransformer(
|
|||
private val clickIntents: WalletClickIntents,
|
||||
private val walletImageResolver: WalletImageResolver,
|
||||
private val getWalletIconUseCase: GetWalletIconUseCase,
|
||||
private val isMainScreenQrScanningEnabled: Boolean = false,
|
||||
) : WalletScreenStateTransformer {
|
||||
|
||||
private val walletLoadingStateFactory by lazy {
|
||||
|
|
@ -60,7 +63,20 @@ internal class InitializeWalletsTransformer(
|
|||
|
||||
private fun createTopBarConfig(): WalletTopBarConfig {
|
||||
return WalletTopBarConfig(
|
||||
onDetailsClick = clickIntents::onDetailsClick,
|
||||
endActions = listOfNotNull(
|
||||
if (isMainScreenQrScanningEnabled) {
|
||||
TangemTopBarActionUM(
|
||||
iconRes = CoreUiR.drawable.ic_qrcode_scaner_24,
|
||||
onClick = clickIntents::onScanQrClick,
|
||||
)
|
||||
} else {
|
||||
null
|
||||
},
|
||||
TangemTopBarActionUM(
|
||||
iconRes = CoreUiR.drawable.ic_more_default_24,
|
||||
onClick = clickIntents::onDetailsClick,
|
||||
),
|
||||
).toPersistentList(),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -112,7 +128,7 @@ internal class InitializeWalletsTransformer(
|
|||
|
||||
private fun UserWallet.toLockedWalletUM(): WalletUM.Locked {
|
||||
return WalletUM.Locked(
|
||||
walletsBalanceUM = WalletBalanceUM.Loading(
|
||||
walletsBalanceUM = WalletBalanceUM.Empty(
|
||||
id = walletId,
|
||||
name = name,
|
||||
deviceIcon = getWalletIconUseCase.invoke(userWallet = this)
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ import com.tangem.core.ui.components.containers.pullToRefresh.PullToRefreshConfi
|
|||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.model.*
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.utils.enableButtons
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletUM
|
||||
import kotlinx.collections.immutable.PersistentList
|
||||
import kotlinx.collections.immutable.mutate
|
||||
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import com.tangem.domain.appcurrency.model.AppCurrency
|
|||
import com.tangem.domain.card.common.util.getCardsCount
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.tokens.error.TokenListError
|
||||
import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents
|
||||
import com.tangem.feature.wallet.presentation.wallet.domain.WalletAdditionalInfoFactory
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.model.*
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.utils.disableButtons
|
||||
|
|
@ -19,6 +20,7 @@ internal class SetTokenListErrorTransformer(
|
|||
private val selectedWallet: UserWallet,
|
||||
private val error: TokenListError,
|
||||
private val appCurrency: AppCurrency,
|
||||
private val clickIntents: WalletClickIntents,
|
||||
) : WalletStateTransformer(selectedWallet.walletId) {
|
||||
|
||||
override fun transform(prevState: WalletState): WalletState {
|
||||
|
|
@ -57,7 +59,11 @@ internal class SetTokenListErrorTransformer(
|
|||
is WalletUM.Content -> {
|
||||
walletUM.copy(
|
||||
walletsBalanceUM = walletUM.walletsBalanceUM.toLoadedState(),
|
||||
tokensListUM = WalletTokensListUM.Empty,
|
||||
tokensListUM = WalletTokensListUM.Empty(
|
||||
onEmptyClick = {
|
||||
clickIntents.onManageTokensClick(walletUM.walletsBalanceUM.id)
|
||||
},
|
||||
),
|
||||
buttons = walletUM.disableButtons(),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ import com.tangem.domain.staking.model.StakingAvailability
|
|||
import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.model.*
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.transformers.converter.MultiWalletBalanceUMTransformer
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletUM
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.transformers.converter.MultiWalletCardStateConverter
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.transformers.converter.TokenListStateConverter
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.transformers.converter.WalletTokensListUMConverter
|
||||
|
|
@ -99,7 +98,13 @@ internal class SetTokenListTransformer(
|
|||
}
|
||||
|
||||
private fun toLoadedState(): WalletTokensListUM {
|
||||
if (params !is TokenConverterParams.Account) return WalletTokensListUM.Empty
|
||||
if (params !is TokenConverterParams.Account) {
|
||||
return WalletTokensListUM.Empty(
|
||||
onEmptyClick = {
|
||||
clickIntents.onManageTokensClick(userWallet.walletId)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
return WalletTokensListUMConverter(
|
||||
selectedWallet = userWallet,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,52 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.state.transformers
|
||||
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.wrappedList
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.feature.wallet.impl.R
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletAdditionalInfo
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletCardState
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletState
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletUM
|
||||
|
||||
internal class SetTokenSyncProgressTransformer(
|
||||
userWalletId: UserWalletId,
|
||||
private val progressPercent: Int,
|
||||
) : WalletStateTransformer(userWalletId) {
|
||||
|
||||
override fun transform(prevState: WalletState): WalletState {
|
||||
return when (prevState) {
|
||||
is WalletState.MultiCurrency.Content -> {
|
||||
val updatedCardState = updateCardState(prevState.walletCardState)
|
||||
prevState.copy(walletCardState = updatedCardState)
|
||||
}
|
||||
else -> {
|
||||
prevState
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun transform(walletUM: WalletUM): WalletUM {
|
||||
return walletUM
|
||||
}
|
||||
|
||||
private fun updateCardState(cardState: WalletCardState): WalletCardState {
|
||||
val additionalInfo = WalletAdditionalInfo(
|
||||
hideable = false,
|
||||
content = resourceReference(
|
||||
id = R.string.initial_wallet_sync_restore_progress,
|
||||
formatArgs = wrappedList(progressPercent),
|
||||
),
|
||||
shouldShowProgress = true,
|
||||
)
|
||||
return when (cardState) {
|
||||
is WalletCardState.Loading -> {
|
||||
cardState.copy(additionalInfo = additionalInfo)
|
||||
}
|
||||
is WalletCardState.Content -> {
|
||||
cardState.copy(additionalInfo = additionalInfo)
|
||||
}
|
||||
else -> cardState
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -46,38 +46,38 @@ internal class WalletTokenCurrencyItemConverter(
|
|||
stakingApyMap = stakingAvailabilityMap,
|
||||
)
|
||||
|
||||
override fun convert(currencyStatus: CryptoCurrencyStatus): TangemTokenRowUM {
|
||||
val earnApyInfo = earnApyConverter.convert(currencyStatus)
|
||||
override fun convert(value: CryptoCurrencyStatus): TangemTokenRowUM {
|
||||
val earnApyInfo = earnApyConverter.convert(value)
|
||||
|
||||
return TangemTokenRowUM.Content(
|
||||
id = currencyStatus.currency.id.value,
|
||||
id = value.currency.id.value,
|
||||
headIconUM = TangemIconUM.Currency(
|
||||
currencyIconState = currencyToIconStateConverter.convert(currencyStatus),
|
||||
currencyIconState = currencyToIconStateConverter.convert(value),
|
||||
),
|
||||
titleUM = toCurrencyRowTitle(currencyStatus, earnApyInfo),
|
||||
subtitleUM = toCurrencyRowSubtitle(currencyStatus),
|
||||
topEndContentUM = toCurrencyRowTopEnd(currencyStatus),
|
||||
bottomEndContentUM = toCurrencyRowBottomEnd(currencyStatus),
|
||||
titleUM = toCurrencyRowTitle(value, earnApyInfo),
|
||||
subtitleUM = toCurrencyRowSubtitle(value),
|
||||
topEndContentUM = toCurrencyRowTopEnd(value),
|
||||
bottomEndContentUM = toCurrencyRowBottomEnd(value),
|
||||
promoBannerUM = toPromoBannerUM(
|
||||
accountId,
|
||||
currencyStatus,
|
||||
value,
|
||||
earnApyInfo.takeIf { shouldShowPromo },
|
||||
),
|
||||
onItemClick = when (currencyStatus.value) {
|
||||
onItemClick = when (value.value) {
|
||||
CryptoCurrencyStatus.Loading,
|
||||
is CryptoCurrencyStatus.MissedDerivation,
|
||||
-> null
|
||||
else -> {
|
||||
{
|
||||
clickIntents.onTokenItemClick(accountId, currencyStatus)
|
||||
clickIntents.onTokenItemClick(accountId, value)
|
||||
}
|
||||
}
|
||||
},
|
||||
onItemLongClick = when (currencyStatus.value) {
|
||||
onItemLongClick = when (value.value) {
|
||||
CryptoCurrencyStatus.Loading -> null
|
||||
else -> {
|
||||
{
|
||||
clickIntents.onTokenItemLongClick(accountId, currencyStatus)
|
||||
clickIntents.onTokenItemLongClick(accountId, value)
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
@ -280,6 +280,8 @@ internal class WalletTokenCurrencyItemConverter(
|
|||
R.string.yield_module_main_screen_promo_banner_message,
|
||||
wrappedList(earnApyInfo.apy),
|
||||
),
|
||||
iconRes = R.drawable.ic_yield_mode_mini_12,
|
||||
type = TangemTokenRowUM.PromoBannerUM.Content.Type.Yield,
|
||||
onPromoBannerClick = {
|
||||
clickIntents.onYieldPromoClicked(currency)
|
||||
clickIntents.onApyLabelClick(
|
||||
|
|
|
|||
|
|
@ -71,8 +71,10 @@ internal class WalletTokensListUMConverter(
|
|||
|
||||
override fun convert(value: AccountStatusList): WalletTokensListUM {
|
||||
val promoCryptoCurrency = yieldSupplyPromoBannerConverter.convert2(value = value)
|
||||
return if (value.accountStatuses.isEmpty()) {
|
||||
WalletTokensListUM.Empty
|
||||
return if (value.flattenCurrencies().isEmpty()) {
|
||||
WalletTokensListUM.Empty(
|
||||
onEmptyClick = { clickIntents.onManageTokensClick(value.mainAccount.accountId) },
|
||||
)
|
||||
} else {
|
||||
val isCollapsable = value.accountStatuses.count {
|
||||
it is AccountStatus.CryptoPortfolio && it.account.tokensCount > 0
|
||||
|
|
@ -89,6 +91,7 @@ internal class WalletTokensListUMConverter(
|
|||
tokenRowUM = accountRowConverter.convert(accountStatus),
|
||||
isExpanded = isExpanded || !isCollapsable,
|
||||
isCollapsable = isCollapsable,
|
||||
onEmptyClick = { clickIntents.onManageTokensClick(accountStatus.account.accountId) },
|
||||
tokenList = getTokenListItems(
|
||||
accountStatus,
|
||||
promoCryptoCurrency,
|
||||
|
|
@ -163,7 +166,7 @@ internal class WalletTokensListUMConverter(
|
|||
return if (accountList.flattenCurrencies().size > 1 && !selectedWallet.isSingleWalletWithToken()) {
|
||||
TangemButtonUM(
|
||||
text = resourceReference(R.string.organize_tokens_title),
|
||||
isEnabled = accountList.totalFiatBalance !is TotalFiatBalance.Loading,
|
||||
isEnabled = accountList.totalFiatBalance is TotalFiatBalance.Loading,
|
||||
size = TangemButtonSize.X9,
|
||||
shape = TangemButtonShape.Rounded,
|
||||
type = TangemButtonType.PrimaryInverse,
|
||||
|
|
|
|||
|
|
@ -132,6 +132,7 @@ internal abstract class BasicAccountListSubscriber : BasicWalletSubscriber() {
|
|||
selectedWallet = userWallet,
|
||||
error = e,
|
||||
appCurrency = appCurrency,
|
||||
clickIntents = clickIntents,
|
||||
),
|
||||
)
|
||||
return
|
||||
|
|
|
|||
|
|
@ -10,6 +10,9 @@ import com.tangem.core.res.getStringSafe
|
|||
import com.tangem.core.ui.event.EventEffect
|
||||
import com.tangem.core.ui.event.StateEvent
|
||||
import com.tangem.core.ui.extensions.resolveReference
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.message.SnackbarMessage
|
||||
import com.tangem.core.ui.res.LocalTopSnackbarHostState
|
||||
import com.tangem.core.ui.utils.requestPermission
|
||||
import com.tangem.feature.wallet.impl.R
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletEvent
|
||||
|
|
@ -69,18 +72,17 @@ internal fun WalletEventEffectLegacy(
|
|||
@Composable
|
||||
internal fun WalletEventEffect(
|
||||
walletsPagerState: PagerState,
|
||||
snackbarHostState: SnackbarHostState,
|
||||
event: StateEvent<WalletEvent>,
|
||||
onCollapseBalance: () -> Unit,
|
||||
) {
|
||||
val resources = LocalContext.current.resources
|
||||
|
||||
var showPermissionRequest by remember { mutableStateOf<Pair<() -> Unit, () -> Unit>?>(null) }
|
||||
HandlePermissionRequest(
|
||||
permissionRequestParams = showPermissionRequest,
|
||||
onPermissionRequestResult = { showPermissionRequest = null },
|
||||
)
|
||||
|
||||
val tangemTopSnackbarHostState = LocalTopSnackbarHostState.current
|
||||
|
||||
EventEffect(
|
||||
event = event,
|
||||
onTrigger = { value ->
|
||||
|
|
@ -92,12 +94,14 @@ internal fun WalletEventEffect(
|
|||
walletsPagerState.scrollToPage(page = value.newIndex)
|
||||
}
|
||||
is WalletEvent.ShowError -> {
|
||||
snackbarHostState.showSnackbar(message = value.text.resolveReference(resources))
|
||||
tangemTopSnackbarHostState.showSnackbar(SnackbarMessage(message = value.text))
|
||||
}
|
||||
is WalletEvent.CopyAddress -> {
|
||||
snackbarHostState.showSnackbar(
|
||||
message = resources.getStringSafe(R.string.wallet_notification_address_copied),
|
||||
duration = SnackbarDuration.Short,
|
||||
tangemTopSnackbarHostState.showSnackbar(
|
||||
SnackbarMessage(
|
||||
startIconId = R.drawable.ic_check_24,
|
||||
message = resourceReference(R.string.wallet_notification_address_copied),
|
||||
),
|
||||
)
|
||||
}
|
||||
is WalletEvent.DemonstrateWalletsScrollPreview -> {
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import androidx.compose.foundation.Canvas
|
|||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.gestures.detectTapGestures
|
||||
import androidx.compose.foundation.isSystemInDarkTheme
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.LazyListScope
|
||||
|
|
@ -20,16 +21,17 @@ import androidx.compose.material3.*
|
|||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.drawBehind
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.draw.shadow
|
||||
import androidx.compose.ui.focus.onFocusChanged
|
||||
import androidx.compose.ui.geometry.*
|
||||
import androidx.compose.ui.geometry.CornerRadius
|
||||
import androidx.compose.ui.geometry.Rect
|
||||
import androidx.compose.ui.geometry.RoundRect
|
||||
import androidx.compose.ui.geometry.Size
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.Outline
|
||||
import androidx.compose.ui.graphics.Path
|
||||
import androidx.compose.ui.graphics.Shape
|
||||
import androidx.compose.ui.unit.Density
|
||||
import androidx.compose.ui.unit.LayoutDirection
|
||||
import androidx.compose.ui.input.pointer.pointerInput
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
|
||||
|
|
@ -40,19 +42,17 @@ import androidx.compose.ui.text.style.TextAlign
|
|||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.DpSize
|
||||
import androidx.compose.ui.unit.IntOffset
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.*
|
||||
import androidx.paging.compose.collectAsLazyPagingItems
|
||||
import com.tangem.common.ui.bottomsheet.chooseaddress.ChooseAddressBottomSheet
|
||||
import com.tangem.common.ui.bottomsheet.chooseaddress.ChooseAddressBottomSheetConfig
|
||||
import com.tangem.common.ui.expressStatus.ExpressStatusBottomSheet
|
||||
import com.tangem.common.ui.expressStatus.ExpressStatusBottomSheetConfig
|
||||
import com.tangem.common.ui.expressStatus.expressTransactionsItems
|
||||
import com.tangem.core.ui.components.atoms.Hand
|
||||
import com.tangem.core.ui.decompose.ComposableContentComponent
|
||||
import com.tangem.core.ui.components.atoms.handComposableComponentHeight
|
||||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
|
||||
import com.tangem.core.ui.components.bottomsheets.sheet.TangemBottomSheetDraggableHeaderLegacy
|
||||
import com.tangem.core.ui.components.bottomsheets.state.BottomSheetState
|
||||
import com.tangem.core.ui.components.containers.pullToRefresh.TangemPullToRefreshContainer
|
||||
import com.tangem.core.ui.components.rememberIsKeyboardVisible
|
||||
|
|
@ -61,6 +61,7 @@ import com.tangem.core.ui.components.snackbar.CopiedTextSnackbar
|
|||
import com.tangem.core.ui.components.snackbar.TangemSnackbar
|
||||
import com.tangem.core.ui.components.transactions.state.TxHistoryState
|
||||
import com.tangem.core.ui.event.StateEvent
|
||||
import com.tangem.core.ui.extensions.softLayerShadow
|
||||
import com.tangem.core.ui.extensions.stringResourceSafe
|
||||
import com.tangem.core.ui.res.LocalMainBottomSheetColor
|
||||
import com.tangem.core.ui.res.LocalWindowSize
|
||||
|
|
@ -91,6 +92,7 @@ import kotlin.math.roundToInt
|
|||
@Composable
|
||||
internal fun WalletScreen(
|
||||
state: WalletScreenState,
|
||||
promoBannersBlockComponent: ComposableContentComponent? = null,
|
||||
bottomSheetContent: @Composable (() -> Unit),
|
||||
bottomSheetHeaderHeightProvider: () -> Dp,
|
||||
onBottomSheetStateChange: (BottomSheetState) -> Unit,
|
||||
|
|
@ -108,6 +110,7 @@ internal fun WalletScreen(
|
|||
snackbarHostState = snackbarHostState,
|
||||
isAutoScroll = isAutoScroll,
|
||||
onAutoScrollReset = { isAutoScroll.value = false },
|
||||
promoBannersBlockComponent = promoBannersBlockComponent,
|
||||
bottomSheetContent = bottomSheetContent,
|
||||
bottomSheetHeaderHeightProvider = bottomSheetHeaderHeightProvider,
|
||||
onBottomSheetStateChange = onBottomSheetStateChange,
|
||||
|
|
@ -129,6 +132,7 @@ private fun WalletContent(
|
|||
snackbarHostState: SnackbarHostState,
|
||||
isAutoScroll: State<Boolean>,
|
||||
onAutoScrollReset: () -> Unit,
|
||||
promoBannersBlockComponent: ComposableContentComponent? = null,
|
||||
bottomSheetHeaderHeightProvider: () -> Dp,
|
||||
onBottomSheetStateChange: (BottomSheetState) -> Unit,
|
||||
bottomSheetContent: @Composable (() -> Unit),
|
||||
|
|
@ -210,6 +214,12 @@ private fun WalletContent(
|
|||
|
||||
notifications(configs = selectedWallet.warnings, modifier = itemModifier)
|
||||
|
||||
promoBannersBlockComponent?.let { component ->
|
||||
item(key = "PromoBannersBlock") {
|
||||
component.Content(modifier = itemModifier)
|
||||
}
|
||||
}
|
||||
|
||||
if (selectedWallet is WalletState.MultiCurrency) {
|
||||
item(
|
||||
key = "TangemPayMainScreenBlock",
|
||||
|
|
@ -290,15 +300,8 @@ private inline fun BaseScaffoldWithMarkets(
|
|||
crossinline bottomSheetContent: @Composable () -> Unit,
|
||||
crossinline content: @Composable (PaddingValues) -> Unit,
|
||||
) {
|
||||
val bottomSheetState = rememberTangemStandardBottomSheetState()
|
||||
|
||||
val isKeyboardVisible by rememberIsKeyboardVisible()
|
||||
|
||||
val scaffoldState = rememberTangemBottomSheetScaffoldState(
|
||||
bottomSheetState = bottomSheetState,
|
||||
snackbarHostState = snackbarHostState,
|
||||
)
|
||||
|
||||
val density = LocalDensity.current
|
||||
val bottomBarHeight = with(density) { WindowInsets.systemBars.getBottom(density = this).toDp() }
|
||||
val statusBarHeight = with(density) { WindowInsets.statusBars.getTop(density = this).toDp() }
|
||||
|
|
@ -308,6 +311,12 @@ private inline fun BaseScaffoldWithMarkets(
|
|||
val coroutineScope = rememberCoroutineScope()
|
||||
val background = TangemTheme.colors.background.tertiary
|
||||
|
||||
val bottomSheetState = rememberTangemStandardBottomSheetState()
|
||||
val scaffoldState = rememberTangemBottomSheetScaffoldState(
|
||||
bottomSheetState = bottomSheetState,
|
||||
snackbarHostState = snackbarHostState,
|
||||
)
|
||||
|
||||
val showMarketsHint by remember {
|
||||
derivedStateOf {
|
||||
// Show hint only when there are items in the list
|
||||
|
|
@ -321,7 +330,7 @@ private inline fun BaseScaffoldWithMarkets(
|
|||
CompositionLocalProvider(
|
||||
LocalMainBottomSheetColor provides remember(background) { mutableStateOf(background) },
|
||||
) {
|
||||
val backgroundColor = LocalMainBottomSheetColor.current
|
||||
val backgroundColor by LocalMainBottomSheetColor.current
|
||||
var isSearchFieldFocused by remember { mutableStateOf(false) }
|
||||
val isNavBarVisible = remember { mutableStateOf(true) }
|
||||
|
||||
|
|
@ -343,43 +352,61 @@ private inline fun BaseScaffoldWithMarkets(
|
|||
.navigationBarsPadding(),
|
||||
)
|
||||
},
|
||||
containerColor = TangemTheme.colors.background.secondary,
|
||||
sheetContainerColor = backgroundColor.value,
|
||||
scaffoldState = scaffoldState,
|
||||
sheetPeekHeight = peekHeight,
|
||||
sheetShape = TangemTheme.shapes.bottomSheetLarge,
|
||||
sheetContent = {
|
||||
// hide bottom sheet when back pressed
|
||||
BackHandler(
|
||||
isKeyboardVisible.not() &&
|
||||
bottomSheetState.currentValue == TangemSheetValue.Expanded,
|
||||
) {
|
||||
coroutineScope.launch { bottomSheetState.partialExpand() }
|
||||
}
|
||||
|
||||
Column(
|
||||
containerColor = TangemTheme.colors.background.secondary,
|
||||
scaffoldState = scaffoldState,
|
||||
bottomSheet = {
|
||||
CustomBottomSheet(
|
||||
state = scaffoldState.bottomSheetState,
|
||||
peekHeight = peekHeight,
|
||||
modifier = Modifier
|
||||
// expand bottom sheet when clicked on the header
|
||||
.clickable(
|
||||
enabled = bottomSheetState.currentValue == TangemSheetValue.PartiallyExpanded,
|
||||
indication = null,
|
||||
interactionSource = null,
|
||||
.softLayerShadow(
|
||||
radius = 8.dp,
|
||||
color = Color.Black.copy(
|
||||
alpha = if (isSystemInDarkTheme()) .16f else .08f,
|
||||
),
|
||||
shape = TangemTheme.shapes.bottomSheetLarge,
|
||||
offset = DpOffset(x = 0.dp, y = (-4).dp),
|
||||
isAlphaContentClip = true,
|
||||
)
|
||||
.clip(TangemTheme.shapes.bottomSheetLarge)
|
||||
.background(backgroundColor),
|
||||
content = {
|
||||
// hide bottom sheet when back pressed
|
||||
BackHandler(
|
||||
isKeyboardVisible.not() &&
|
||||
bottomSheetState.currentValue == TangemSheetValue.Expanded,
|
||||
) {
|
||||
coroutineScope.launch { bottomSheetState.expand() }
|
||||
coroutineScope.launch { bottomSheetState.partialExpand() }
|
||||
}
|
||||
.sizeIn(maxHeight = maxHeight - statusBarHeight),
|
||||
) {
|
||||
Hand(Modifier.drawBehind { drawRect(backgroundColor.value) })
|
||||
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.onFocusChanged {
|
||||
isSearchFieldFocused = it.isFocused
|
||||
},
|
||||
) {
|
||||
bottomSheetContent()
|
||||
}
|
||||
}
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
// expand bottom sheet when clicked on the header
|
||||
.clickable(
|
||||
enabled = bottomSheetState.currentValue == TangemSheetValue.PartiallyExpanded,
|
||||
indication = null,
|
||||
interactionSource = null,
|
||||
) {
|
||||
coroutineScope.launch { bottomSheetState.expand() }
|
||||
}
|
||||
.sizeIn(maxHeight = maxHeight - statusBarHeight),
|
||||
) {
|
||||
TangemBottomSheetDraggableHeaderLegacy(backgroundColor)
|
||||
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.onFocusChanged {
|
||||
isSearchFieldFocused = it.isFocused
|
||||
},
|
||||
) {
|
||||
bottomSheetContent()
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
},
|
||||
content = { paddingValues ->
|
||||
Box {
|
||||
|
|
@ -428,7 +455,7 @@ private inline fun BaseScaffoldWithMarkets(
|
|||
Box(
|
||||
Modifier
|
||||
.align(Alignment.BottomCenter)
|
||||
.background(backgroundColor.value)
|
||||
.background(backgroundColor)
|
||||
.height(bottomBarHeight)
|
||||
.fillMaxWidth(),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -17,18 +17,21 @@ import androidx.compose.foundation.lazy.rememberLazyListState
|
|||
import androidx.compose.foundation.pager.HorizontalPager
|
||||
import androidx.compose.foundation.pager.PagerState
|
||||
import androidx.compose.foundation.pager.rememberPagerState
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.SnackbarHost
|
||||
import androidx.compose.material3.SnackbarHostState
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.pulltorefresh.rememberPullToRefreshState
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.alpha
|
||||
import androidx.compose.ui.draw.drawBehind
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.focus.FocusState
|
||||
import androidx.compose.ui.focus.onFocusChanged
|
||||
import androidx.compose.ui.graphics.Brush
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.graphicsLayer
|
||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||
import androidx.compose.ui.input.pointer.pointerInput
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
|
|
@ -40,25 +43,23 @@ import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
|||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.arkivanov.decompose.ExperimentalDecomposeApi
|
||||
import com.tangem.core.ui.components.atoms.Hand
|
||||
import com.tangem.core.ui.components.BottomFade
|
||||
import com.tangem.core.ui.components.atoms.handComposableComponentHeight
|
||||
import com.tangem.core.ui.components.background.northernlights.NorthernLightsBackground
|
||||
import com.tangem.core.ui.components.bottomsheets.sheet.TangemBottomSheetDraggableHeader
|
||||
import com.tangem.core.ui.components.bottomsheets.state.BottomSheetState
|
||||
import com.tangem.core.ui.components.containers.pullToRefresh.TangemPullToRefreshSlidingContainer
|
||||
import com.tangem.core.ui.components.haze.hazeSourceTangem
|
||||
import com.tangem.core.ui.components.rememberIsKeyboardVisible
|
||||
import com.tangem.core.ui.components.sheetscaffold.*
|
||||
import com.tangem.core.ui.components.snackbar.CopiedTextSnackbar
|
||||
import com.tangem.core.ui.components.snackbar.TangemSnackbar
|
||||
import com.tangem.core.ui.ds.topbar.collapsing.TangemCollapsingAppBarBehavior
|
||||
import com.tangem.core.ui.ds.topbar.collapsing.TangemCollapsingTopBar
|
||||
import com.tangem.core.ui.ds.topbar.collapsing.rememberTangemExitUntilCollapsedScrollBehavior
|
||||
import com.tangem.core.ui.event.StateEvent
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.res.*
|
||||
import com.tangem.feature.wallet.presentation.common.preview.WalletScreenPreviewData
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.model.NOT_INITIALIZED_WALLET_INDEX
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletBalanceUM
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletEvent
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletScreenState
|
||||
import com.tangem.feature.wallet.presentation.wallet.ui.components.MarketsHint
|
||||
import com.tangem.feature.wallet.presentation.wallet.ui.components.MarketsTooltip
|
||||
|
|
@ -85,7 +86,6 @@ internal fun WalletScreen2(
|
|||
|
||||
val statusBarHeight = with(LocalDensity.current) { WindowInsets.systemBars.getTop(this).toDp() }
|
||||
|
||||
val snackbarHostState = remember(::SnackbarHostState)
|
||||
val walletsPagerState = rememberPagerState(
|
||||
initialPage = state.selectedWalletIndex,
|
||||
pageCount = { state.wallets2.size },
|
||||
|
|
@ -104,7 +104,6 @@ internal fun WalletScreen2(
|
|||
WalletContent2(
|
||||
state = state,
|
||||
walletsPagerState = walletsPagerState,
|
||||
snackbarHostState = snackbarHostState,
|
||||
behavior = behavior,
|
||||
bottomSheetContent = bottomSheetContent,
|
||||
bottomSheetHeaderHeightProvider = bottomSheetHeaderHeightProvider,
|
||||
|
|
@ -113,7 +112,6 @@ internal fun WalletScreen2(
|
|||
|
||||
WalletEventEffect(
|
||||
walletsPagerState = walletsPagerState,
|
||||
snackbarHostState = snackbarHostState,
|
||||
event = state.event,
|
||||
onCollapseBalance = {
|
||||
if (behavior.state.collapsedFraction < 1f) {
|
||||
|
|
@ -131,7 +129,6 @@ private fun WalletContent2(
|
|||
state: WalletScreenState,
|
||||
walletsPagerState: PagerState,
|
||||
behavior: TangemCollapsingAppBarBehavior,
|
||||
snackbarHostState: SnackbarHostState,
|
||||
bottomSheetHeaderHeightProvider: () -> Dp,
|
||||
onBottomSheetStateChange: (BottomSheetState) -> Unit,
|
||||
bottomSheetContent: @Composable (() -> Unit),
|
||||
|
|
@ -140,10 +137,14 @@ private fun WalletContent2(
|
|||
val bottomBarHeight = with(density) { WindowInsets.systemBars.getBottom(this).toDp() }
|
||||
|
||||
var walletBalance by remember { mutableStateOf<TextReference?>(TextReference.EMPTY) }
|
||||
var pullToRefreshConfig by remember {
|
||||
mutableStateOf(
|
||||
state.wallets2.getOrNull(state.selectedWalletIndex)?.pullToRefreshConfig,
|
||||
)
|
||||
}
|
||||
|
||||
BaseScaffoldWithMarkets(
|
||||
state = state,
|
||||
snackbarHostState = snackbarHostState,
|
||||
bottomSheetHeaderHeightProvider = bottomSheetHeaderHeightProvider,
|
||||
onBottomSheetStateChange = onBottomSheetStateChange,
|
||||
bottomSheetContent = bottomSheetContent,
|
||||
|
|
@ -175,6 +176,8 @@ private fun WalletContent2(
|
|||
|
||||
val canPagerScroll by remember { derivedStateOf { behavior.state.heightOffset == 0f } }
|
||||
|
||||
val pullToRefreshState = rememberPullToRefreshState()
|
||||
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
|
|
@ -186,11 +189,15 @@ private fun WalletContent2(
|
|||
} else {
|
||||
TangemTheme.colors2.surface.level2
|
||||
},
|
||||
modifier = Modifier.matchParentSize(),
|
||||
modifier = Modifier
|
||||
.graphicsLayer { alpha = 1 - behavior.state.collapsedFraction * 2 }
|
||||
.matchParentSize(),
|
||||
)
|
||||
|
||||
WalletPagerIndicator(
|
||||
pagerState = walletsPagerState,
|
||||
pullToRefreshState = pullToRefreshState,
|
||||
pullToRefreshConfig = pullToRefreshConfig,
|
||||
behavior = behavior,
|
||||
)
|
||||
|
||||
|
|
@ -210,6 +217,11 @@ private fun WalletContent2(
|
|||
walletBalance = (currentWallet.walletsBalanceUM as? WalletBalanceUM.Content)?.balanceInAppBar
|
||||
}
|
||||
}
|
||||
LaunchedEffect(walletsPagerState.currentPage, currentWallet.pullToRefreshConfig) {
|
||||
if (walletsPagerState.currentPage == currentWalletIndex) {
|
||||
pullToRefreshConfig = currentWallet.pullToRefreshConfig
|
||||
}
|
||||
}
|
||||
|
||||
val isShowMarketsHint by remember {
|
||||
derivedStateOf {
|
||||
|
|
@ -222,8 +234,13 @@ private fun WalletContent2(
|
|||
|
||||
val pageSlideAlpha by rememberPageAlpha(walletsPagerState, currentWalletIndex)
|
||||
|
||||
Box(
|
||||
TangemPullToRefreshSlidingContainer(
|
||||
state = pullToRefreshState,
|
||||
config = currentWallet.pullToRefreshConfig,
|
||||
modifier = Modifier.alpha(pageSlideAlpha),
|
||||
indicatorOffset = with(LocalDensity.current) {
|
||||
behavior.state.partialHeightLimit.toDp()
|
||||
},
|
||||
) {
|
||||
TangemCollapsingTopBar(
|
||||
state = behavior.state,
|
||||
|
|
@ -278,7 +295,6 @@ private fun WalletContent2(
|
|||
@Composable
|
||||
private inline fun BaseScaffoldWithMarkets(
|
||||
state: WalletScreenState,
|
||||
snackbarHostState: SnackbarHostState,
|
||||
bottomSheetHeaderHeightProvider: () -> Dp,
|
||||
modifier: Modifier = Modifier,
|
||||
noinline onBottomSheetStateChange: (BottomSheetState) -> Unit,
|
||||
|
|
@ -286,28 +302,20 @@ private inline fun BaseScaffoldWithMarkets(
|
|||
crossinline bottomSheetContent: @Composable () -> Unit,
|
||||
crossinline content: @Composable (PaddingValues, TangemSheetState) -> Unit,
|
||||
) {
|
||||
val bottomSheetState = rememberTangemStandardBottomSheetState()
|
||||
|
||||
val isKeyboardVisible by rememberIsKeyboardVisible()
|
||||
|
||||
val scaffoldState = rememberTangemBottomSheetScaffoldState(
|
||||
bottomSheetState = bottomSheetState,
|
||||
snackbarHostState = snackbarHostState,
|
||||
)
|
||||
|
||||
val density = LocalDensity.current
|
||||
val bottomBarHeight = with(density) { WindowInsets.systemBars.getBottom(density = this).toDp() }
|
||||
val statusBarHeight = with(density) { WindowInsets.statusBars.getTop(density = this).toDp() }
|
||||
val peekHeight = bottomSheetHeaderHeightProvider() + handComposableComponentHeight + bottomBarHeight
|
||||
val maxHeight = LocalWindowSize.current.height
|
||||
val peekHeight = bottomSheetHeaderHeightProvider() + TangemTheme.dimens2.x3 + bottomBarHeight
|
||||
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
val background = TangemTheme.colors2.surface.level3
|
||||
val background = TangemTheme.colors2.surface.level2
|
||||
|
||||
val bottomSheetState = rememberTangemStandardBottomSheetState()
|
||||
val scaffoldState = rememberTangemBottomSheetScaffoldState(bottomSheetState = bottomSheetState)
|
||||
|
||||
CompositionLocalProvider(
|
||||
LocalMainBottomSheetColor provides remember(background) { mutableStateOf(background) },
|
||||
) {
|
||||
val backgroundColor = LocalMainBottomSheetColor.current
|
||||
val backgroundColor by LocalMainBottomSheetColor.current
|
||||
var isSearchFieldFocused by remember { mutableStateOf(false) }
|
||||
val isNavBarVisible = remember { mutableStateOf(true) }
|
||||
|
||||
|
|
@ -320,51 +328,19 @@ private inline fun BaseScaffoldWithMarkets(
|
|||
|
||||
Box(modifier = modifier) {
|
||||
TangemBottomSheetScaffold(
|
||||
snackbarHost = { snackbarHostState ->
|
||||
WalletSnackbarHost(
|
||||
snackbarHostState = snackbarHostState,
|
||||
event = state.event,
|
||||
modifier = Modifier
|
||||
.padding(bottom = TangemTheme.dimens2.x1)
|
||||
.navigationBarsPadding(),
|
||||
)
|
||||
},
|
||||
containerColor = Color.Unspecified,
|
||||
sheetContainerColor = backgroundColor.value,
|
||||
scaffoldState = scaffoldState,
|
||||
sheetPeekHeight = peekHeight,
|
||||
sheetShape = TangemTheme.shapes.bottomSheetLarge,
|
||||
sheetContent = {
|
||||
// hide bottom sheet when back pressed
|
||||
BackHandler(
|
||||
isKeyboardVisible.not() &&
|
||||
bottomSheetState.currentValue == TangemSheetValue.Expanded,
|
||||
bottomSheet = {
|
||||
BottomSheet(
|
||||
bottomSheetState = bottomSheetState,
|
||||
backgroundColor = backgroundColor,
|
||||
peekHeight = peekHeight,
|
||||
onFocusChange = { focusState ->
|
||||
isSearchFieldFocused = focusState.isFocused
|
||||
},
|
||||
) {
|
||||
coroutineScope.launch { bottomSheetState.partialExpand() }
|
||||
}
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
// expand bottom sheet when clicked on the header
|
||||
.clickable(
|
||||
enabled = bottomSheetState.currentValue == TangemSheetValue.PartiallyExpanded,
|
||||
indication = null,
|
||||
interactionSource = null,
|
||||
) {
|
||||
coroutineScope.launch { bottomSheetState.expand() }
|
||||
}
|
||||
.sizeIn(maxHeight = maxHeight - statusBarHeight),
|
||||
) {
|
||||
Hand(Modifier.drawBehind { drawRect(backgroundColor.value) })
|
||||
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.onFocusChanged {
|
||||
isSearchFieldFocused = it.isFocused
|
||||
},
|
||||
) {
|
||||
bottomSheetContent()
|
||||
}
|
||||
bottomSheetContent()
|
||||
}
|
||||
},
|
||||
content = { paddingValues ->
|
||||
|
|
@ -388,7 +364,7 @@ private inline fun BaseScaffoldWithMarkets(
|
|||
Box(
|
||||
Modifier
|
||||
.align(Alignment.BottomCenter)
|
||||
.background(backgroundColor.value)
|
||||
.background(backgroundColor)
|
||||
.height(bottomBarHeight)
|
||||
.fillMaxWidth(),
|
||||
)
|
||||
|
|
@ -403,6 +379,81 @@ private inline fun BaseScaffoldWithMarkets(
|
|||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun BottomSheet(
|
||||
bottomSheetState: TangemSheetState,
|
||||
backgroundColor: Color,
|
||||
peekHeight: Dp,
|
||||
onFocusChange: (FocusState) -> Unit,
|
||||
bottomSheetContent: @Composable () -> Unit,
|
||||
) {
|
||||
val isKeyboardVisible by rememberIsKeyboardVisible()
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
val density = LocalDensity.current
|
||||
val statusBarHeight = with(density) { WindowInsets.statusBars.getTop(density = this).toDp() }
|
||||
|
||||
val maxHeight = LocalWindowSize.current.height
|
||||
val shape = RoundedCornerShape(
|
||||
topStart = TangemTheme.dimens2.x8,
|
||||
topEnd = TangemTheme.dimens2.x8,
|
||||
)
|
||||
CustomBottomSheet(
|
||||
state = bottomSheetState,
|
||||
peekHeight = peekHeight,
|
||||
content = {
|
||||
// hide bottom sheet when back pressed
|
||||
BackHandler(
|
||||
isKeyboardVisible.not() &&
|
||||
bottomSheetState.currentValue == TangemSheetValue.Expanded,
|
||||
) {
|
||||
coroutineScope.launch { bottomSheetState.partialExpand() }
|
||||
}
|
||||
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
modifier = Modifier
|
||||
// expand bottom sheet when clicked on the header
|
||||
.clickable(
|
||||
enabled = bottomSheetState.currentValue == TangemSheetValue.PartiallyExpanded,
|
||||
indication = null,
|
||||
interactionSource = null,
|
||||
) {
|
||||
coroutineScope.launch { bottomSheetState.expand() }
|
||||
}
|
||||
.sizeIn(maxHeight = maxHeight - statusBarHeight),
|
||||
) {
|
||||
Box(modifier = Modifier.fillMaxWidth()) {
|
||||
BottomFade(
|
||||
gradientBrush = Brush.verticalGradient(
|
||||
colors = listOf(
|
||||
TangemTheme.colors2.shadow.min,
|
||||
TangemTheme.colors2.shadow.max,
|
||||
),
|
||||
),
|
||||
modifier = Modifier
|
||||
.offset(y = TangemTheme.dimens2.x5.unaryMinus()),
|
||||
)
|
||||
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
TangemBottomSheetDraggableHeader()
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clip(shape)
|
||||
.background(backgroundColor)
|
||||
.onFocusChanged(onFocusChange),
|
||||
) {
|
||||
bottomSheetContent()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun BottomSheetScrim(color: Color, visible: Boolean, onDismissRequest: () -> Unit) {
|
||||
val alpha by animateFloatAsState(
|
||||
|
|
@ -482,21 +533,6 @@ private fun BottomSheetStateEffects(
|
|||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun WalletSnackbarHost(
|
||||
snackbarHostState: SnackbarHostState,
|
||||
event: StateEvent<WalletEvent>,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
SnackbarHost(hostState = snackbarHostState, modifier = modifier) { data ->
|
||||
if (event is StateEvent.Triggered && event.data is WalletEvent.CopyAddress) {
|
||||
CopiedTextSnackbar(data)
|
||||
} else {
|
||||
TangemSnackbar(data)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun rememberPageAlpha(pagerState: PagerState, currentPageIndex: Int): State<Float> {
|
||||
return remember {
|
||||
|
|
@ -542,7 +578,10 @@ private class WalletScreen2PreviewProvider : PreviewParameterProvider<WalletScre
|
|||
override val values: Sequence<WalletScreenState>
|
||||
get() = sequenceOf(
|
||||
WalletScreenPreviewData.defaultState,
|
||||
WalletScreenPreviewData.defaultState.copy(selectedWalletIndex = 1),
|
||||
WalletScreenPreviewData.emptyState,
|
||||
WalletScreenPreviewData.defaultAccountState,
|
||||
WalletScreenPreviewData.emptyAccountState,
|
||||
WalletScreenPreviewData.lockedState,
|
||||
)
|
||||
}
|
||||
// endregion
|
||||
|
|
@ -5,9 +5,10 @@ import androidx.compose.animation.AnimatedVisibility
|
|||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.animation.fadeIn
|
||||
import androidx.compose.animation.fadeOut
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.text.InlineTextContent
|
||||
import androidx.compose.foundation.text.appendInlineContent
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
|
|
@ -15,17 +16,13 @@ import androidx.compose.ui.Alignment
|
|||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.res.vectorResource
|
||||
import androidx.compose.ui.text.Placeholder
|
||||
import androidx.compose.ui.text.PlaceholderVerticalAlign
|
||||
import androidx.compose.ui.text.buildAnnotatedString
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import com.tangem.core.ui.extensions.stringResourceSafe
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreviewRedesign
|
||||
import com.tangem.feature.wallet.impl.R
|
||||
|
||||
private const val STARS_INLINE_CONTENT_ID = "stars"
|
||||
|
||||
@Composable
|
||||
internal fun MarketsHint(isVisible: Boolean, modifier: Modifier = Modifier) {
|
||||
AnimatedVisibility(
|
||||
|
|
@ -36,41 +33,28 @@ internal fun MarketsHint(isVisible: Boolean, modifier: Modifier = Modifier) {
|
|||
) {
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens2.x1),
|
||||
) {
|
||||
Text(
|
||||
text = "Swipe up to explore the market", // todo redesign main lokalise
|
||||
style = TangemTheme.typography2.bodyRegular14,
|
||||
text = stringResourceSafe(R.string.markets_hint_part_one),
|
||||
style = TangemTheme.typography2.bodyRegular15,
|
||||
color = TangemTheme.colors2.text.neutral.primary,
|
||||
textAlign = TextAlign.Center,
|
||||
)
|
||||
Text(
|
||||
text = buildAnnotatedString {
|
||||
append("Find new hidden gems ") // todo redesign main lokalise
|
||||
appendInlineContent(
|
||||
STARS_INLINE_CONTENT_ID,
|
||||
alternateText = "\uDBC0\uDDBF",
|
||||
)
|
||||
},
|
||||
inlineContent = mapOf(
|
||||
STARS_INLINE_CONTENT_ID to InlineTextContent(
|
||||
placeholder = Placeholder(
|
||||
width = TangemTheme.typography2.bodyRegular14.fontSize,
|
||||
height = TangemTheme.typography2.bodyRegular14.fontSize,
|
||||
placeholderVerticalAlign = PlaceholderVerticalAlign.Center,
|
||||
),
|
||||
children = {
|
||||
Icon(
|
||||
imageVector = ImageVector.vectorResource(R.drawable.ic_magic_default_24),
|
||||
tint = TangemTheme.colors2.text.neutral.tertiary,
|
||||
contentDescription = null,
|
||||
)
|
||||
},
|
||||
),
|
||||
),
|
||||
style = TangemTheme.typography2.bodyRegular14,
|
||||
color = TangemTheme.colors2.text.neutral.tertiary,
|
||||
textAlign = TextAlign.Center,
|
||||
)
|
||||
Row(horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens2.x1)) {
|
||||
Text(
|
||||
text = stringResourceSafe(R.string.markets_hint_part_two),
|
||||
style = TangemTheme.typography2.bodyRegular15,
|
||||
color = TangemTheme.colors2.text.neutral.tertiary,
|
||||
textAlign = TextAlign.Center,
|
||||
)
|
||||
Icon(
|
||||
imageVector = ImageVector.vectorResource(R.drawable.ic_magic_default_24),
|
||||
tint = TangemTheme.colors2.text.neutral.tertiary,
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(TangemTheme.dimens2.x5),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ internal fun LazyListScope.organizeTokens2(state: WalletUM, itemModifier: Modifi
|
|||
contentType = "OrganizeTokensButton",
|
||||
) {
|
||||
TangemButton(
|
||||
organizeButton,
|
||||
buttonUM = organizeButton,
|
||||
modifier = itemModifier,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import androidx.compose.animation.core.tween
|
|||
import androidx.compose.animation.fadeIn
|
||||
import androidx.compose.animation.fadeOut
|
||||
import androidx.compose.animation.togetherWith
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.text.TextAutoSize
|
||||
import androidx.compose.material3.Text
|
||||
|
|
@ -28,6 +29,7 @@ import com.tangem.core.ui.ds.button.SecondaryTangemButton
|
|||
import com.tangem.core.ui.ds.button.TangemButtonShape
|
||||
import com.tangem.core.ui.ds.button.TangemButtonUM
|
||||
import com.tangem.core.ui.ds.image.TangemDeviceIcon
|
||||
import com.tangem.core.ui.ds.placeholder.TextPlaceholder
|
||||
import com.tangem.core.ui.ds.topbar.collapsing.TangemCollapsingAppBarBehavior
|
||||
import com.tangem.core.ui.ds.topbar.collapsing.rememberTangemExitUntilCollapsedScrollBehavior
|
||||
import com.tangem.core.ui.ds.topbar.collapsing.snapToExitUntilCollapsed
|
||||
|
|
@ -75,7 +77,7 @@ internal fun WalletBalance(
|
|||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(200.dp),
|
||||
.padding(vertical = 58.dp),
|
||||
) {
|
||||
Balance(
|
||||
walletBalanceUM = walletBalanceUM,
|
||||
|
|
@ -112,44 +114,40 @@ private fun Balance(walletBalanceUM: WalletBalanceUM, isBalanceHidden: Boolean,
|
|||
},
|
||||
) { balanceUM ->
|
||||
when (balanceUM) {
|
||||
is WalletBalanceUM.Content -> {
|
||||
Text(
|
||||
text = balanceUM.balance.orMaskWithStars(isBalanceHidden).resolveAnnotatedReference(),
|
||||
style = TangemTheme.typography2.titleRegular44.applyBladeBrush(
|
||||
isEnabled = balanceUM.isBalanceFlickering,
|
||||
textColor = TangemTheme.colors2.text.neutral.primary,
|
||||
),
|
||||
color = TangemTheme.colors2.text.neutral.primary,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
autoSize = TextAutoSize.StepBased(
|
||||
minFontSize = TangemTheme.typography2.bodySemibold15.fontSize,
|
||||
maxFontSize = TangemTheme.typography2.titleRegular44.fontSize,
|
||||
),
|
||||
)
|
||||
}
|
||||
is WalletBalanceUM.Error -> {
|
||||
Text(
|
||||
text = StringsSigns.DASH_SIGN,
|
||||
style = TangemTheme.typography2.titleRegular44,
|
||||
color = TangemTheme.colors2.text.neutral.primary,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
autoSize = TextAutoSize.StepBased(
|
||||
minFontSize = TangemTheme.typography2.bodySemibold15.fontSize,
|
||||
maxFontSize = TangemTheme.typography2.titleRegular44.fontSize,
|
||||
),
|
||||
)
|
||||
}
|
||||
is WalletBalanceUM.Loading,
|
||||
-> {
|
||||
TextShimmer(
|
||||
text = "123456",
|
||||
style = TangemTheme.typography2.titleRegular44,
|
||||
radius = TangemTheme.dimens2.x25,
|
||||
textSizeHeight = true,
|
||||
)
|
||||
}
|
||||
is WalletBalanceUM.Content -> Text(
|
||||
text = balanceUM.balance.orMaskWithStars(isBalanceHidden).resolveAnnotatedReference(),
|
||||
style = TangemTheme.typography2.titleRegular44.applyBladeBrush(
|
||||
isEnabled = balanceUM.isBalanceFlickering,
|
||||
textColor = TangemTheme.colors2.text.neutral.primary,
|
||||
),
|
||||
color = TangemTheme.colors2.text.neutral.primary,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
autoSize = TextAutoSize.StepBased(
|
||||
minFontSize = TangemTheme.typography2.bodySemibold15.fontSize,
|
||||
maxFontSize = TangemTheme.typography2.titleRegular44.fontSize,
|
||||
),
|
||||
)
|
||||
is WalletBalanceUM.Error -> Text(
|
||||
text = StringsSigns.DASH_SIGN,
|
||||
style = TangemTheme.typography2.titleRegular44,
|
||||
color = TangemTheme.colors2.text.neutral.primary,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
autoSize = TextAutoSize.StepBased(
|
||||
minFontSize = TangemTheme.typography2.bodySemibold15.fontSize,
|
||||
maxFontSize = TangemTheme.typography2.titleRegular44.fontSize,
|
||||
),
|
||||
)
|
||||
is WalletBalanceUM.Loading -> TextShimmer(
|
||||
text = "123456",
|
||||
style = TangemTheme.typography2.titleRegular44,
|
||||
radius = TangemTheme.dimens2.x25,
|
||||
)
|
||||
is WalletBalanceUM.Empty -> TextPlaceholder(
|
||||
textStyle = TangemTheme.typography2.titleRegular44,
|
||||
width = 200.dp,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -162,6 +160,11 @@ private fun ActionButtons(buttons: ImmutableList<TangemButtonUM>) {
|
|||
) {
|
||||
buttons.fastForEach { button ->
|
||||
key(button.text) {
|
||||
val textColor = if (button.isEnabled) {
|
||||
TangemTheme.colors2.text.neutral.primary
|
||||
} else {
|
||||
TangemTheme.colors2.text.status.disabled
|
||||
}
|
||||
Column(
|
||||
modifier = Modifier.padding(horizontal = TangemTheme.dimens2.x2_5),
|
||||
verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens2.x2),
|
||||
|
|
@ -170,12 +173,13 @@ private fun ActionButtons(buttons: ImmutableList<TangemButtonUM>) {
|
|||
SecondaryTangemButton(
|
||||
iconRes = button.iconRes,
|
||||
onClick = button.onClick,
|
||||
isEnabled = button.isEnabled,
|
||||
shape = TangemButtonShape.Rounded,
|
||||
)
|
||||
Text(
|
||||
text = button.text.orEmpty().resolveReference(),
|
||||
style = TangemTheme.typography2.bodySemibold15,
|
||||
color = TangemTheme.colors2.text.neutral.primary,
|
||||
color = textColor,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -187,24 +191,34 @@ private fun ActionButtons(buttons: ImmutableList<TangemButtonUM>) {
|
|||
@Composable
|
||||
@Preview(showBackground = true, widthDp = 360)
|
||||
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
private fun WalletBalance_Preview(@PreviewParameter(WalletBalancePreviewProvider::class) params: WalletBalanceUM) {
|
||||
private fun WalletBalance_Preview(
|
||||
@PreviewParameter(WalletBalancePreviewProvider::class)
|
||||
params: WalletBalancePreviewData,
|
||||
) {
|
||||
TangemThemePreviewRedesign {
|
||||
WalletBalance(
|
||||
walletBalanceUM = params,
|
||||
walletBalanceUM = params.walletBalanceUM,
|
||||
behavior = rememberTangemExitUntilCollapsedScrollBehavior(),
|
||||
buttons = WalletPreviewData.actionButtons,
|
||||
buttons = params.actionsList,
|
||||
isBalanceHidden = false,
|
||||
modifier = Modifier.background(TangemTheme.colors2.surface.level1),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private class WalletBalancePreviewProvider : PreviewParameterProvider<WalletBalanceUM> {
|
||||
override val values: Sequence<WalletBalanceUM>
|
||||
private data class WalletBalancePreviewData(
|
||||
val walletBalanceUM: WalletBalanceUM,
|
||||
val actionsList: ImmutableList<TangemButtonUM>,
|
||||
)
|
||||
|
||||
private class WalletBalancePreviewProvider : PreviewParameterProvider<WalletBalancePreviewData> {
|
||||
override val values: Sequence<WalletBalancePreviewData>
|
||||
get() = sequenceOf(
|
||||
WalletBalancePreview.content,
|
||||
WalletBalancePreview.content.copy(isBalanceFlickering = true),
|
||||
WalletBalancePreview.loading,
|
||||
WalletBalancePreview.error,
|
||||
WalletBalancePreviewData(WalletBalancePreview.content, WalletPreviewData.actionButtons),
|
||||
WalletBalancePreviewData(WalletBalancePreview.hiddenBalanceContent, WalletPreviewData.actionButtons),
|
||||
WalletBalancePreviewData(WalletBalancePreview.loading, WalletPreviewData.disabledActionButtons),
|
||||
WalletBalancePreviewData(WalletBalancePreview.error, WalletPreviewData.disabledActionButtons),
|
||||
WalletBalancePreviewData(WalletBalancePreview.empty, WalletPreviewData.disabledActionButtons),
|
||||
)
|
||||
}
|
||||
// endregion
|
||||
|
|
@ -12,6 +12,7 @@ import androidx.compose.foundation.indication
|
|||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.interaction.PressInteraction
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.foundation.text.TextAutoSize
|
||||
import androidx.compose.material3.*
|
||||
|
|
@ -164,6 +165,7 @@ private fun CardContainer(state: WalletCardState, isBalanceHidden: Boolean, item
|
|||
}
|
||||
AdditionalInfo(
|
||||
text = additionalText,
|
||||
showProgress = state.additionalInfo?.shouldShowProgress == true,
|
||||
modifier = Modifier.conditional(
|
||||
state.imageResId == null,
|
||||
) { fillMaxWidth() },
|
||||
|
|
@ -298,7 +300,7 @@ private fun Modifier.nonContentBalanceSize(dimens: TangemDimens): Modifier {
|
|||
}
|
||||
|
||||
@Composable
|
||||
private fun AdditionalInfo(text: TextReference?, modifier: Modifier = Modifier) {
|
||||
private fun AdditionalInfo(text: TextReference?, showProgress: Boolean, modifier: Modifier = Modifier) {
|
||||
AnimatedContent(
|
||||
targetState = text,
|
||||
label = "Update the additional text",
|
||||
|
|
@ -309,7 +311,19 @@ private fun AdditionalInfo(text: TextReference?, modifier: Modifier = Modifier)
|
|||
},
|
||||
) { animatedText ->
|
||||
if (animatedText != null) {
|
||||
AdditionalInfoText(text = animatedText)
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.spacedBy(6.dp),
|
||||
) {
|
||||
AdditionalInfoText(text = animatedText)
|
||||
if (showProgress) {
|
||||
CircularProgressIndicator(
|
||||
modifier = Modifier
|
||||
.size(TangemTheme.dimens.size16),
|
||||
color = TangemTheme.colors.icon.accent,
|
||||
strokeWidth = TangemTheme.dimens.size2,
|
||||
)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
RectangleShimmer(modifier = Modifier.nonContentAdditionalInfoSize(dimens = TangemTheme.dimens))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import com.tangem.common.ui.notifications.notificationsCarousel
|
|||
import com.tangem.core.ui.components.transactions.state.TxHistoryState
|
||||
import com.tangem.core.ui.components.transactions.txHistoryItems
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.utils.TangemSharedTransitionLayout
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletState
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletUM
|
||||
import com.tangem.feature.wallet.presentation.wallet.ui.components.multicurrency.tokensListItems
|
||||
|
|
@ -37,39 +38,40 @@ internal fun WalletListContent(
|
|||
val movableItemModifier = Modifier.padding(horizontal = TangemTheme.dimens2.x3)
|
||||
val itemModifier = movableItemModifier.padding(top = TangemTheme.dimens2.x3)
|
||||
|
||||
LazyColumn(
|
||||
modifier = modifier,
|
||||
state = listState,
|
||||
contentPadding = contentPadding,
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
overscrollEffect = rememberOverscrollEffect(),
|
||||
) {
|
||||
notifications(
|
||||
notifications = currentWallet.notifications.map { it.messageUM }.toPersistentList(),
|
||||
contentColor = containerColor,
|
||||
modifier = movableItemModifier,
|
||||
)
|
||||
notificationsCarousel(
|
||||
containerColor = containerColor,
|
||||
modifier = movableItemModifier,
|
||||
notifications = currentWallet.notificationsCarousel.map { it.messageUM }.toPersistentList(),
|
||||
)
|
||||
TangemSharedTransitionLayout(modifier) {
|
||||
LazyColumn(
|
||||
state = listState,
|
||||
contentPadding = contentPadding,
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
overscrollEffect = rememberOverscrollEffect(),
|
||||
) {
|
||||
notifications(
|
||||
notifications = currentWallet.notifications.map { it.messageUM }.toPersistentList(),
|
||||
contentColor = containerColor,
|
||||
modifier = movableItemModifier,
|
||||
)
|
||||
notificationsCarousel(
|
||||
containerColor = containerColor,
|
||||
modifier = movableItemModifier,
|
||||
notifications = currentWallet.notificationsCarousel.map { it.messageUM }.toPersistentList(),
|
||||
)
|
||||
|
||||
tangemPay(
|
||||
walletUM = currentWallet,
|
||||
isBalanceHiding = isBalanceHidden,
|
||||
modifier = itemModifier,
|
||||
)
|
||||
tangemPay(
|
||||
walletUM = currentWallet,
|
||||
isBalanceHiding = isBalanceHidden,
|
||||
modifier = itemModifier,
|
||||
)
|
||||
|
||||
tokensListItems2(
|
||||
walletTokensListUM = currentWallet.tokensListUM,
|
||||
modifier = movableItemModifier,
|
||||
isBalanceHidden = isBalanceHidden,
|
||||
)
|
||||
tokensListItems2(
|
||||
walletTokensListUM = currentWallet.tokensListUM,
|
||||
modifier = movableItemModifier,
|
||||
isBalanceHidden = isBalanceHidden,
|
||||
)
|
||||
|
||||
nftCollections2(state = currentWallet, itemModifier = itemModifier)
|
||||
nftCollections2(state = currentWallet, itemModifier = itemModifier)
|
||||
|
||||
organizeTokens2(state = currentWallet, itemModifier = itemModifier)
|
||||
organizeTokens2(state = currentWallet, itemModifier = itemModifier)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,49 +1,71 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.ui.components.common
|
||||
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.fadeIn
|
||||
import androidx.compose.animation.fadeOut
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.pager.PagerState
|
||||
import androidx.compose.material3.pulltorefresh.PullToRefreshState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.alpha
|
||||
import androidx.compose.ui.draw.scale
|
||||
import androidx.compose.ui.graphics.graphicsLayer
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.tangem.core.ui.components.containers.pullToRefresh.PullToRefreshConfig
|
||||
import com.tangem.core.ui.components.containers.pullToRefresh.getPullToRefreshIndicatorOffset
|
||||
import com.tangem.core.ui.ds.TangemPagerIndicator
|
||||
import com.tangem.core.ui.ds.topbar.collapsing.TangemCollapsingAppBarBehavior
|
||||
|
||||
private const val MIN_SCALE = 0.75f
|
||||
private const val MAX_SCALE = 1f
|
||||
private const val WALLET_INDICATOR_OFFSET = 0.6f
|
||||
|
||||
@Composable
|
||||
internal fun WalletPagerIndicator(pagerState: PagerState, behavior: TangemCollapsingAppBarBehavior) {
|
||||
internal fun WalletPagerIndicator(
|
||||
pagerState: PagerState,
|
||||
behavior: TangemCollapsingAppBarBehavior,
|
||||
pullToRefreshConfig: PullToRefreshConfig?,
|
||||
pullToRefreshState: PullToRefreshState,
|
||||
) {
|
||||
val collapsedFraction = behavior.state.collapsedFraction
|
||||
val alpha = MAX_SCALE - collapsedFraction
|
||||
val scale = alpha.coerceIn(MIN_SCALE, MAX_SCALE)
|
||||
val height = with(LocalDensity.current) {
|
||||
behavior.state.heightOffsetLimit.toDp().unaryMinus()
|
||||
}
|
||||
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.graphicsLayer {
|
||||
scaleY = scale
|
||||
translationY = behavior.state.heightOffset
|
||||
}
|
||||
.fillMaxWidth()
|
||||
.height(
|
||||
with(LocalDensity.current) {
|
||||
behavior.state.heightOffsetLimit.toDp().unaryMinus()
|
||||
},
|
||||
)
|
||||
.alpha(alpha),
|
||||
val contentOffset = getPullToRefreshIndicatorOffset(
|
||||
pullToRefreshConfig = pullToRefreshConfig,
|
||||
pullToRefreshState = pullToRefreshState,
|
||||
)
|
||||
val padding = height * WALLET_INDICATOR_OFFSET
|
||||
|
||||
AnimatedVisibility(
|
||||
visible = pagerState.pageCount > 1,
|
||||
enter = fadeIn(),
|
||||
exit = fadeOut(),
|
||||
) {
|
||||
TangemPagerIndicator(
|
||||
pagerState = pagerState,
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.padding(top = 248.dp)
|
||||
.scale(scaleY = 1f, scaleX = scale)
|
||||
.fillMaxWidth(),
|
||||
)
|
||||
.graphicsLayer {
|
||||
scaleY = scale
|
||||
translationY = behavior.state.heightOffset + contentOffset.toPx()
|
||||
}
|
||||
.fillMaxWidth()
|
||||
.height(height)
|
||||
.alpha(alpha),
|
||||
) {
|
||||
TangemPagerIndicator(
|
||||
pagerState = pagerState,
|
||||
modifier = Modifier
|
||||
.padding(top = padding)
|
||||
.scale(scaleY = 1f, scaleX = scale)
|
||||
.fillMaxWidth(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,22 +1,33 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.ui.components.common
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.animation.core.animateFloatAsState
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.statusBarsPadding
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.lerp
|
||||
import androidx.compose.ui.platform.testTag
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import com.tangem.core.ui.components.haze.hazeEffectTangem
|
||||
import com.tangem.core.ui.ds.topbar.TangemTopBar
|
||||
import com.tangem.core.ui.ds.topbar.TangemTopBarActionContent
|
||||
import com.tangem.core.ui.ds.topbar.TangemTopBarActionUM
|
||||
import com.tangem.core.ui.ds.topbar.collapsing.TangemCollapsingAppBarBehavior
|
||||
import com.tangem.core.ui.ds.topbar.collapsing.rememberTangemExitUntilCollapsedScrollBehavior
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.core.ui.res.LocalRootBackgroundColor
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.core.ui.res.TangemThemePreviewRedesign
|
||||
|
|
@ -25,6 +36,8 @@ import com.tangem.feature.wallet.impl.R
|
|||
import com.tangem.feature.wallet.presentation.common.WalletPreviewDataLegacy
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletTopBarConfig
|
||||
import dev.chrisbanes.haze.HazeProgressive
|
||||
import dev.chrisbanes.haze.HazeTint
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
|
||||
private const val VISIBILITY_THRESHOLD = 0.5f
|
||||
|
||||
|
|
@ -44,26 +57,44 @@ internal fun WalletTopBar(
|
|||
Surface(
|
||||
color = Color.Unspecified,
|
||||
contentColor = Color.Unspecified,
|
||||
modifier = Modifier.hazeEffectTangem {
|
||||
progressive = HazeProgressive.verticalGradient(startIntensity = 1f, endIntensity = 0f)
|
||||
},
|
||||
modifier = Modifier.hazeEffectTangemTopBar(behavior),
|
||||
) {
|
||||
val wrappedBalance = remember(behavior.state.collapsedFraction) {
|
||||
if (behavior.state.collapsedFraction > VISIBILITY_THRESHOLD) walletBalance else null
|
||||
val isWrappedBalanceShown by remember {
|
||||
derivedStateOf { behavior.state.collapsedFraction > VISIBILITY_THRESHOLD }
|
||||
}
|
||||
|
||||
val wrappedBalance = remember(walletBalance, isWrappedBalanceShown) {
|
||||
walletBalance.takeIf { isWrappedBalanceShown }
|
||||
}
|
||||
|
||||
TangemTopBar(
|
||||
title = wrappedBalance,
|
||||
startActionUM = TangemTopBarActionUM(
|
||||
iconRes = R.drawable.ic_tangem_24,
|
||||
isActionable = false,
|
||||
),
|
||||
endActionUM = TangemTopBarActionUM(
|
||||
iconRes = R.drawable.ic_more_default_24,
|
||||
isActionable = true,
|
||||
onClick = topBarConfig.onDetailsClick,
|
||||
ghostModeProgress = behavior.state.collapsedFraction,
|
||||
),
|
||||
startContent = {
|
||||
TangemTopBarActionContent(
|
||||
TangemTopBarActionUM(
|
||||
iconRes = R.drawable.ic_tangem_24,
|
||||
isActionable = false,
|
||||
),
|
||||
)
|
||||
},
|
||||
endContent = {
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens2.x5),
|
||||
modifier = Modifier
|
||||
.clip(CircleShape)
|
||||
.background(
|
||||
lerp(
|
||||
start = Color.Transparent,
|
||||
stop = TangemTheme.colors2.button.backgroundSecondary,
|
||||
fraction = behavior.state.collapsedFraction,
|
||||
),
|
||||
),
|
||||
) {
|
||||
topBarConfig.endActions.forEach { action ->
|
||||
TangemTopBarActionContent(action)
|
||||
}
|
||||
}
|
||||
},
|
||||
modifier = Modifier
|
||||
.statusBarsPadding()
|
||||
.testTag(MainScreenTestTags.TOP_BAR),
|
||||
|
|
@ -85,8 +116,16 @@ internal fun WalletTopBar(config: WalletTopBarConfig) {
|
|||
Icon(painter = painterResource(id = R.drawable.img_tangem_logo_90_24), contentDescription = null)
|
||||
},
|
||||
actions = {
|
||||
IconButton(onClick = config.onDetailsClick, modifier = Modifier.testTag(MainScreenTestTags.MORE_BUTTON)) {
|
||||
Icon(painter = painterResource(id = R.drawable.ic_more_vertical_24), contentDescription = null)
|
||||
config.endActions.forEach { action ->
|
||||
action.onClick?.let { onClick ->
|
||||
IconButton(onClick = onClick) {
|
||||
Icon(
|
||||
painter = painterResource(id = action.iconRes),
|
||||
contentDescription = null,
|
||||
modifier = Modifier.testTag(MainScreenTestTags.MORE_BUTTON),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
colors = TopAppBarDefaults.topAppBarColors(
|
||||
|
|
@ -99,6 +138,21 @@ internal fun WalletTopBar(config: WalletTopBarConfig) {
|
|||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun Modifier.hazeEffectTangemTopBar(behavior: TangemCollapsingAppBarBehavior): Modifier {
|
||||
val rootBackground by LocalRootBackgroundColor.current
|
||||
val intensity by animateFloatAsState(targetValue = behavior.state.collapsedFraction * 2f)
|
||||
|
||||
return hazeEffectTangem {
|
||||
fallbackTint = HazeTint(rootBackground.copy(alpha = intensity / 2))
|
||||
progressive = HazeProgressive.verticalGradient(
|
||||
startIntensity = intensity,
|
||||
endIntensity = 0f,
|
||||
preferPerformance = true,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
|
|
@ -115,7 +169,31 @@ private fun Preview_WalletTopBar() {
|
|||
private fun WalletTopBar_Preview() {
|
||||
TangemThemePreviewRedesign {
|
||||
WalletTopBar(
|
||||
topBarConfig = WalletTopBarConfig(onDetailsClick = {}),
|
||||
topBarConfig = WalletTopBarConfig(),
|
||||
walletBalance = stringReference("$ 8923,05"),
|
||||
behavior = rememberTangemExitUntilCollapsedScrollBehavior(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
@Preview(showBackground = true, widthDp = 360)
|
||||
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
private fun WalletTopBar_WithQrButton_Preview() {
|
||||
TangemThemePreviewRedesign {
|
||||
WalletTopBar(
|
||||
topBarConfig = WalletTopBarConfig(
|
||||
endActions = persistentListOf(
|
||||
TangemTopBarActionUM(
|
||||
iconRes = R.drawable.ic_qrcode_scaner_24,
|
||||
onClick = {},
|
||||
),
|
||||
TangemTopBarActionUM(
|
||||
iconRes = R.drawable.ic_more_default_24,
|
||||
onClick = {},
|
||||
),
|
||||
),
|
||||
),
|
||||
walletBalance = stringReference("$ 8923,05"),
|
||||
behavior = rememberTangemExitUntilCollapsedScrollBehavior(),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -2,6 +2,9 @@ package com.tangem.feature.wallet.presentation.wallet.ui.components.multicurrenc
|
|||
|
||||
import androidx.compose.animation.*
|
||||
import androidx.compose.animation.core.FastOutLinearInEasing
|
||||
import androidx.compose.animation.core.LinearOutSlowInEasing
|
||||
import androidx.compose.animation.core.animateIntAsState
|
||||
import androidx.compose.animation.core.snap
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.padding
|
||||
|
|
@ -26,7 +29,6 @@ import com.tangem.core.ui.res.TangemTheme
|
|||
import com.tangem.core.ui.test.MainScreenTestTags
|
||||
import com.tangem.core.ui.utils.lazyListItemPosition
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.coroutines.delay
|
||||
|
||||
internal fun LazyListScope.portfolioContentItems(
|
||||
items: ImmutableList<TokensListItemUM.Portfolio>,
|
||||
|
|
@ -155,18 +157,17 @@ private fun LazyListScope.portfolioItem(
|
|||
key = "account-${portfolio.id}",
|
||||
contentType = "account-content",
|
||||
) {
|
||||
var lastIndexProxy by remember { mutableIntStateOf(lastIndex) }
|
||||
|
||||
// When collapsing the portfolio, we delay updating lastIndexProxy to allow
|
||||
// shrinking animation to complete before changing the shape.
|
||||
LaunchedEffect(lastIndex) {
|
||||
if (lastIndex != 0) {
|
||||
lastIndexProxy = lastIndex
|
||||
return@LaunchedEffect
|
||||
}
|
||||
delay(timeMillis = minOf(50 * tokens.size, 250).toLong())
|
||||
lastIndexProxy = 0
|
||||
}
|
||||
// Snap immediately on expand; on collapse, hold until all child items finish
|
||||
// their shrink animation, then snap to fully-rounded shape.
|
||||
val effectiveLastIndex by animateIntAsState(
|
||||
targetValue = lastIndex,
|
||||
animationSpec = if (lastIndex != 0) {
|
||||
snap()
|
||||
} else {
|
||||
snap(delayMillis = minOf(50 * tokens.lastIndex, 250) + 150)
|
||||
},
|
||||
label = "lastIndex",
|
||||
)
|
||||
|
||||
PortfolioListItem(
|
||||
state = portfolio,
|
||||
|
|
@ -176,7 +177,7 @@ private fun LazyListScope.portfolioItem(
|
|||
.roundedShapeItemDecoration(
|
||||
currentIndex = 0,
|
||||
radius = TangemTheme.dimens.radius14,
|
||||
lastIndex = lastIndexProxy,
|
||||
lastIndex = effectiveLastIndex,
|
||||
backgroundColor = TangemTheme.colors.background.primary,
|
||||
),
|
||||
)
|
||||
|
|
@ -199,18 +200,14 @@ internal fun SlideInItemVisibility(
|
|||
AnimatedVisibility(
|
||||
modifier = modifier,
|
||||
visible = visible,
|
||||
enter = fadeIn(
|
||||
tween(100, delayMillis = delayEnter),
|
||||
) + expandVertically(
|
||||
tween(100, delayMillis = delayEnter, easing = FastOutLinearInEasing),
|
||||
enter = expandVertically(
|
||||
tween(200, delayMillis = delayEnter, easing = LinearOutSlowInEasing),
|
||||
expandFrom = Alignment.Top,
|
||||
),
|
||||
exit = fadeOut(
|
||||
tween(100, delayMillis = delayExit),
|
||||
) + shrinkVertically(
|
||||
tween(100, delayMillis = delayExit, easing = FastOutLinearInEasing),
|
||||
) + fadeIn(tween(200, delayMillis = delayEnter, easing = LinearOutSlowInEasing)),
|
||||
exit = shrinkVertically(
|
||||
tween(150, delayMillis = delayExit, easing = FastOutLinearInEasing),
|
||||
shrinkTowards = Alignment.Top,
|
||||
),
|
||||
) + fadeOut(tween(150, delayMillis = delayExit, easing = FastOutLinearInEasing)),
|
||||
) {
|
||||
content()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@ package com.tangem.feature.wallet.presentation.wallet.ui.components.multicurrenc
|
|||
import androidx.compose.animation.*
|
||||
import androidx.compose.animation.SharedTransitionScope.ResizeMode.Companion.scaleToBounds
|
||||
import androidx.compose.animation.core.animateFloatAsState
|
||||
import androidx.compose.animation.core.animateIntAsState
|
||||
import androidx.compose.animation.core.snap
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
|
|
@ -15,6 +17,7 @@ import androidx.compose.material3.Text
|
|||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.platform.testTag
|
||||
import androidx.compose.ui.res.painterResource
|
||||
|
|
@ -23,12 +26,17 @@ import androidx.compose.ui.text.lerp
|
|||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.util.fastForEachIndexed
|
||||
import com.tangem.core.ui.components.SpacerH
|
||||
import com.tangem.core.ui.components.account.AccountIconSize
|
||||
import com.tangem.core.ui.components.currency.icon.CurrencyIconState
|
||||
import com.tangem.core.ui.components.tokenlist.TokenListItem
|
||||
import com.tangem.core.ui.components.tokenlist.state.TokensListItemUM
|
||||
import com.tangem.core.ui.decorations.roundedShapeItemDecoration
|
||||
import com.tangem.core.ui.ds.button.PrimaryInverseTangemButton
|
||||
import com.tangem.core.ui.ds.button.TangemButtonShape
|
||||
import com.tangem.core.ui.ds.button.TangemButtonSize
|
||||
import com.tangem.core.ui.ds.image.TangemIcon
|
||||
import com.tangem.core.ui.ds.image.TangemIconUM
|
||||
import com.tangem.core.ui.ds.row.header.TangemHeaderRow
|
||||
import com.tangem.core.ui.ds.row.header.TangemHeaderRowUM
|
||||
import com.tangem.core.ui.ds.row.internal.TangemRowTailUM
|
||||
|
|
@ -95,6 +103,7 @@ internal fun LazyListScope.tokensListItems2(
|
|||
when (walletTokensListUM) {
|
||||
is WalletTokensListUM.Loading,
|
||||
is WalletTokensListUM.Content,
|
||||
WalletTokensListUM.Locked,
|
||||
-> {
|
||||
walletTokensListUM.tokenList.fastForEachIndexed { index, listItem ->
|
||||
when (listItem) {
|
||||
|
|
@ -116,7 +125,10 @@ internal fun LazyListScope.tokensListItems2(
|
|||
}
|
||||
}
|
||||
}
|
||||
WalletTokensListUM.Empty -> nonContentItem(modifier = modifier)
|
||||
is WalletTokensListUM.Empty -> nonContentItem2(
|
||||
onEmptyClick = walletTokensListUM.onEmptyClick,
|
||||
modifier = modifier,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -164,7 +176,11 @@ private fun LazyListScope.portfolioItem(
|
|||
isBalanceHidden: Boolean,
|
||||
modifier: Modifier,
|
||||
) {
|
||||
val lastIndex = listItem.tokenList.lastIndex + 1
|
||||
val lastIndex = if (listItem.tokenList.isEmpty()) {
|
||||
listItem.tokenList.lastIndex + 2
|
||||
} else {
|
||||
listItem.tokenList.lastIndex + 1
|
||||
}
|
||||
|
||||
accountItem(
|
||||
listItem = listItem,
|
||||
|
|
@ -173,47 +189,57 @@ private fun LazyListScope.portfolioItem(
|
|||
lastIndex = lastIndex,
|
||||
isBalanceHidden = isBalanceHidden,
|
||||
)
|
||||
itemsIndexed(
|
||||
items = listItem.tokenList,
|
||||
key = { _, item -> item.tokenRowUM.id },
|
||||
contentType = { _, item -> item::class.java },
|
||||
itemContent = { tokenIndex, item ->
|
||||
SlideInItemVisibility(
|
||||
currentIndex = tokenIndex + 1,
|
||||
lastIndex = lastIndex,
|
||||
modifier = modifier
|
||||
.animateItem(fadeInSpec = null, placementSpec = null, fadeOutSpec = null)
|
||||
.roundedShapeItemDecoration(
|
||||
radius = 18.dp,
|
||||
currentIndex = tokenIndex + 1,
|
||||
addDefaultPadding = false,
|
||||
lastIndex = lastIndex,
|
||||
backgroundColor = TangemTheme.colors2.surface.level3,
|
||||
),
|
||||
visible = listItem.isExpanded,
|
||||
) {
|
||||
val itemModifier = Modifier
|
||||
.testTag(MainScreenTestTags.TOKEN_LIST_ITEM)
|
||||
.semantics { lazyListItemPosition = tokenIndex + 1 }
|
||||
if (listItem.tokenList.isEmpty()) {
|
||||
nonContentAccountItem(
|
||||
listItem = listItem,
|
||||
index = index,
|
||||
lastIndex = lastIndex,
|
||||
modifier = modifier,
|
||||
)
|
||||
} else {
|
||||
itemsIndexed(
|
||||
items = listItem.tokenList,
|
||||
key = { _, item -> item.tokenRowUM.id },
|
||||
contentType = { _, item -> item::class.java },
|
||||
itemContent = { tokenIndex, item ->
|
||||
SlideInItemVisibility(
|
||||
currentIndex = tokenIndex + 1,
|
||||
lastIndex = lastIndex,
|
||||
modifier = modifier
|
||||
.animateItem(fadeInSpec = null, placementSpec = null, fadeOutSpec = null)
|
||||
.roundedShapeItemDecoration(
|
||||
radius = 18.dp,
|
||||
currentIndex = tokenIndex + 1,
|
||||
addDefaultPadding = false,
|
||||
lastIndex = lastIndex,
|
||||
backgroundColor = TangemTheme.colors2.surface.level3,
|
||||
),
|
||||
visible = listItem.isExpanded,
|
||||
) {
|
||||
val itemModifier = Modifier
|
||||
.testTag(MainScreenTestTags.TOKEN_LIST_ITEM)
|
||||
.semantics { lazyListItemPosition = tokenIndex + 1 }
|
||||
|
||||
when (val tokenRowUM = item.tokenRowUM) {
|
||||
is TangemTokenRowUM -> TangemTokenRow(
|
||||
tokenRowUM = tokenRowUM,
|
||||
isBalanceHidden = isBalanceHidden,
|
||||
reorderableState = null,
|
||||
modifier = itemModifier,
|
||||
)
|
||||
is TangemHeaderRowUM -> TangemHeaderRow(
|
||||
headerRowUM = tokenRowUM,
|
||||
isBalanceHidden = isBalanceHidden,
|
||||
modifier = itemModifier,
|
||||
)
|
||||
when (val tokenRowUM = item.tokenRowUM) {
|
||||
is TangemTokenRowUM -> TangemTokenRow(
|
||||
tokenRowUM = tokenRowUM,
|
||||
isBalanceHidden = isBalanceHidden,
|
||||
reorderableState = null,
|
||||
modifier = itemModifier,
|
||||
)
|
||||
is TangemHeaderRowUM -> TangemHeaderRow(
|
||||
headerRowUM = tokenRowUM,
|
||||
isBalanceHidden = isBalanceHidden,
|
||||
modifier = itemModifier,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
private fun LazyListScope.accountItem(
|
||||
listItem: TokensListItemUM2.Portfolio,
|
||||
modifier: Modifier,
|
||||
|
|
@ -225,6 +251,18 @@ private fun LazyListScope.accountItem(
|
|||
key = listItem.tokenRowUM.id,
|
||||
contentType = listItem.tokenRowUM::class.java,
|
||||
) {
|
||||
// Snap immediately on expand; on collapse, hold the current value until all
|
||||
// child items finish their shrink animation, then snap to fully-rounded shape.
|
||||
val effectiveLastIndex by animateIntAsState(
|
||||
targetValue = if (listItem.isExpanded) lastIndex else 0,
|
||||
animationSpec = if (listItem.isExpanded) {
|
||||
snap()
|
||||
} else {
|
||||
snap(delayMillis = minOf(50 * maxOf(listItem.tokenList.lastIndex, 0), 250) + 150)
|
||||
},
|
||||
label = "lastIndex",
|
||||
)
|
||||
|
||||
val portfolioModifier = modifier
|
||||
.padding(top = if (index != 0) TangemTheme.dimens2.x2 else TangemTheme.dimens2.x3)
|
||||
.testTag(MainScreenTestTags.TOKEN_LIST_ITEM)
|
||||
|
|
@ -233,7 +271,7 @@ private fun LazyListScope.accountItem(
|
|||
currentIndex = 0,
|
||||
radius = 18.dp,
|
||||
addDefaultPadding = false,
|
||||
lastIndex = if (listItem.isExpanded) lastIndex else 0,
|
||||
lastIndex = effectiveLastIndex,
|
||||
backgroundColor = TangemTheme.colors2.surface.level3,
|
||||
)
|
||||
if (listItem.isCollapsable) {
|
||||
|
|
@ -288,10 +326,9 @@ internal fun PortfolioRowItem(
|
|||
isBalanceHidden: Boolean,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
// TangemSharedTransitionLayout {
|
||||
ProvideSharedTransitionScope(modifier) {
|
||||
val iconSharedContentState = rememberSharedContentState(key = "icon")
|
||||
val titleSharedContentState = rememberSharedContentState(key = "title")
|
||||
val iconSharedContentState = rememberSharedContentState(key = "icon_${item.tokenRowUM.id}")
|
||||
val titleSharedContentState = rememberSharedContentState(key = "title_${item.tokenRowUM.id}")
|
||||
val boundsTransform = BoundsTransform { _, _ -> tween(250) }
|
||||
|
||||
AnimatedContent(
|
||||
|
|
@ -307,22 +344,30 @@ internal fun PortfolioRowItem(
|
|||
SharedTokenRowComposables(
|
||||
icon = { modifier ->
|
||||
val size = if (isExpandedWrapped) AccountIconSize.ExtraSmall else AccountIconSize.Default
|
||||
val currencyIconState =
|
||||
when (val currencyIconState = item.tokenRowUM.headIconUM.currencyIconState) {
|
||||
is CurrencyIconState.CryptoPortfolio.Icon ->
|
||||
currencyIconState.copy(size = size)
|
||||
is CurrencyIconState.CryptoPortfolio.Letter ->
|
||||
currencyIconState.copy(size = size)
|
||||
else -> currencyIconState
|
||||
}
|
||||
val headIcon = item.tokenRowUM.headIconUM
|
||||
val sizedHeadIcon = if (headIcon is TangemIconUM.Currency) {
|
||||
headIcon.copy(
|
||||
currencyIconState = when (val currencyIconState = headIcon.currencyIconState) {
|
||||
is CurrencyIconState.CryptoPortfolio.Icon -> currencyIconState.copy(size = size)
|
||||
is CurrencyIconState.CryptoPortfolio.Letter -> currencyIconState.copy(size = size)
|
||||
else -> currencyIconState
|
||||
},
|
||||
)
|
||||
} else {
|
||||
headIcon
|
||||
}
|
||||
|
||||
TangemIcon(
|
||||
tangemIconUM = item.tokenRowUM.headIconUM.copy(currencyIconState = currencyIconState),
|
||||
modifier = modifier.sharedBounds(
|
||||
sharedContentState = iconSharedContentState,
|
||||
animatedVisibilityScope = animatedContentScope,
|
||||
boundsTransform = boundsTransform,
|
||||
),
|
||||
tangemIconUM = sizedHeadIcon,
|
||||
modifier = modifier
|
||||
.conditionalCompose(headIcon is TangemIconUM.Empty) {
|
||||
size(TangemTheme.dimens2.x9)
|
||||
}
|
||||
.sharedBounds(
|
||||
sharedContentState = iconSharedContentState,
|
||||
animatedVisibilityScope = animatedContentScope,
|
||||
boundsTransform = boundsTransform,
|
||||
),
|
||||
)
|
||||
},
|
||||
title = { modifier ->
|
||||
|
|
@ -382,7 +427,6 @@ internal fun PortfolioRowItem(
|
|||
)
|
||||
}
|
||||
}
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -405,6 +449,54 @@ private fun LazyListScope.nonContentItem(modifier: Modifier = Modifier) {
|
|||
}
|
||||
}
|
||||
|
||||
private fun LazyListScope.nonContentItem2(onEmptyClick: () -> Unit, modifier: Modifier = Modifier) {
|
||||
item(
|
||||
key = NON_CONTENT_TOKENS_LIST_KEY,
|
||||
contentType = NON_CONTENT_TOKENS_LIST_KEY,
|
||||
) {
|
||||
NonContentItemContentV2(
|
||||
onClick = onEmptyClick,
|
||||
textColor = TangemTheme.colors2.text.neutral.tertiary,
|
||||
modifier = modifier
|
||||
.animateItem(fadeInSpec = null, fadeOutSpec = null)
|
||||
.padding(top = 46.dp),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun LazyListScope.nonContentAccountItem(
|
||||
listItem: TokensListItemUM2.Portfolio,
|
||||
index: Int,
|
||||
lastIndex: Int,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
item(
|
||||
key = "$NON_CONTENT_TOKENS_LIST_KEY account-${listItem.tokenRowUM.id}",
|
||||
contentType = "$NON_CONTENT_TOKENS_LIST_KEY account-${listItem.tokenRowUM.id}",
|
||||
) {
|
||||
SlideInItemVisibility(
|
||||
currentIndex = index + 1,
|
||||
lastIndex = lastIndex,
|
||||
modifier = modifier
|
||||
.animateItem(fadeInSpec = null, placementSpec = null, fadeOutSpec = null)
|
||||
.roundedShapeItemDecoration(
|
||||
radius = 18.dp,
|
||||
addDefaultPadding = false,
|
||||
currentIndex = 1,
|
||||
lastIndex = 1,
|
||||
backgroundColor = TangemTheme.colors2.surface.level3,
|
||||
),
|
||||
visible = listItem.isExpanded,
|
||||
) {
|
||||
NonContentItemContentV2(
|
||||
modifier = Modifier.padding(vertical = 36.dp),
|
||||
textColor = TangemTheme.colors2.text.neutral.secondary,
|
||||
onClick = listItem.onEmptyClick,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
internal fun NonContentItemContent(modifier: Modifier = Modifier) {
|
||||
Column(
|
||||
|
|
@ -427,4 +519,35 @@ internal fun NonContentItemContent(modifier: Modifier = Modifier) {
|
|||
style = TangemTheme.typography.caption2,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
internal fun NonContentItemContentV2(textColor: Color, modifier: Modifier = Modifier, onClick: () -> Unit) {
|
||||
Column(
|
||||
modifier = modifier,
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(id = R.drawable.ic_empty_64),
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(size = TangemTheme.dimens2.x16),
|
||||
tint = TangemTheme.colors2.graphic.neutral.quaternary,
|
||||
)
|
||||
SpacerH(TangemTheme.dimens2.x5)
|
||||
Text(
|
||||
text = stringResourceSafe(id = R.string.main_empty_tokens_list_message),
|
||||
modifier = Modifier.padding(horizontal = TangemTheme.dimens.spacing48),
|
||||
color = textColor,
|
||||
textAlign = TextAlign.Center,
|
||||
style = TangemTheme.typography2.bodyRegular14,
|
||||
)
|
||||
SpacerH(TangemTheme.dimens2.x2)
|
||||
PrimaryInverseTangemButton(
|
||||
text = resourceReference(id = R.string.common_add_tokens),
|
||||
onClick = onClick,
|
||||
size = TangemButtonSize.X8,
|
||||
shape = TangemButtonShape.Rounded,
|
||||
modifier = Modifier,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -5,7 +5,7 @@ import androidx.compose.runtime.getValue
|
|||
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.message.MessageBottomSheetV2
|
||||
import com.tangem.core.ui.components.bottomsheets.message.MessageBottomSheet
|
||||
import com.tangem.core.ui.decompose.ComposableBottomSheetComponent
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import dagger.assisted.Assisted
|
||||
|
|
@ -26,7 +26,7 @@ internal class KycRejectedComponent @AssistedInject constructor(
|
|||
@Composable
|
||||
override fun BottomSheet() {
|
||||
val state by model.uiState.collectAsStateWithLifecycle()
|
||||
MessageBottomSheetV2(state = state, onDismissRequest = ::dismiss)
|
||||
MessageBottomSheet(state = state, onDismissRequest = ::dismiss)
|
||||
}
|
||||
|
||||
data class Params(
|
||||
|
|
|
|||
|
|
@ -27,15 +27,15 @@ internal class KycRejectedModel @Inject constructor(
|
|||
|
||||
private val params = paramsContainer.require<KycRejectedComponent.Params>()
|
||||
|
||||
val uiState: StateFlow<MessageBottomSheetUMV2>
|
||||
val uiState: StateFlow<MessageBottomSheetUM>
|
||||
field = MutableStateFlow(getInitialState())
|
||||
|
||||
private fun getInitialState(): MessageBottomSheetUMV2 {
|
||||
private fun getInitialState(): MessageBottomSheetUM {
|
||||
return bottomSheetMessage {
|
||||
infoBlock {
|
||||
icon(com.tangem.core.ui.R.drawable.ic_heart_broken_32) {
|
||||
type = MessageBottomSheetUMV2.Icon.Type.Warning
|
||||
backgroundType = MessageBottomSheetUMV2.Icon.BackgroundType.Warning
|
||||
type = MessageBottomSheetUM.Icon.Type.Warning
|
||||
backgroundType = MessageBottomSheetUM.Icon.BackgroundType.Warning
|
||||
}
|
||||
title = resourceReference(R.string.tangempay_kyc_rejected)
|
||||
body = combinedReference(
|
||||
|
|
@ -68,7 +68,7 @@ internal class KycRejectedModel @Inject constructor(
|
|||
onDismiss()
|
||||
}
|
||||
}
|
||||
}.messageBottomSheetUMV2
|
||||
}.messageBottomSheetUM
|
||||
}
|
||||
|
||||
fun onDismiss() {
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import com.tangem.common.ui.userwallet.state.UserWalletItemUM
|
|||
import com.tangem.core.decompose.ui.UiMessageSender
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.message.SnackbarMessage
|
||||
import com.tangem.domain.account.status.usecase.GetWalletTotalBalanceUseCaseV2
|
||||
import com.tangem.domain.account.status.usecase.GetWalletTotalBalanceUseCase
|
||||
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
|
||||
import com.tangem.domain.appcurrency.error.SelectedAppCurrencyError
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
|
|
@ -37,7 +37,7 @@ import kotlinx.coroutines.flow.*
|
|||
@Suppress("LongParameterList")
|
||||
internal class DefaultUserWalletsFetcher @AssistedInject constructor(
|
||||
getWalletsUseCase: GetWalletsUseCase,
|
||||
private val getWalletTotalBalanceUseCaseV2: GetWalletTotalBalanceUseCaseV2,
|
||||
private val getWalletTotalBalanceUseCase: GetWalletTotalBalanceUseCase,
|
||||
private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
|
||||
private val getBalanceHidingSettingsUseCase: GetBalanceHidingSettingsUseCase,
|
||||
@Assisted private val onWalletClick: (UserWalletId) -> Unit,
|
||||
|
|
@ -99,7 +99,7 @@ internal class DefaultUserWalletsFetcher @AssistedInject constructor(
|
|||
// We should not load balances in auth mode
|
||||
flowOf(Lce.Loading(walletIds.associateWith { TotalFiatBalance.Loading }))
|
||||
} else {
|
||||
getWalletTotalBalanceUseCaseV2(userWalletIds = walletIds)
|
||||
getWalletTotalBalanceUseCase(userWalletIds = walletIds)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue