Updated on 2026-08-14
This commit is contained in:
parent
d98b2d98ed
commit
552ad875b1
8 changed files with 675 additions and 0 deletions
|
|
@ -0,0 +1,21 @@
|
||||||
|
package com.tangem.features.managetokens.component
|
||||||
|
|
||||||
|
import com.tangem.core.decompose.factory.ComponentFactory
|
||||||
|
import com.tangem.core.ui.decompose.ComposableContentComponent
|
||||||
|
import com.tangem.domain.models.currency.CryptoCurrency
|
||||||
|
import com.tangem.domain.wallets.models.UserWalletId
|
||||||
|
|
||||||
|
interface ChooseManagedTokensComponent : ComposableContentComponent {
|
||||||
|
|
||||||
|
data class Params(
|
||||||
|
val userWalletId: UserWalletId,
|
||||||
|
val initialCurrency: CryptoCurrency,
|
||||||
|
val source: Source,
|
||||||
|
)
|
||||||
|
|
||||||
|
enum class Source {
|
||||||
|
SendViaSwap,
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Factory : ComponentFactory<Params, ChooseManagedTokensComponent>
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,72 @@
|
||||||
|
package com.tangem.features.managetokens.choosetoken
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
|
import com.arkivanov.decompose.ComponentContext
|
||||||
|
import com.arkivanov.decompose.extensions.compose.subscribeAsState
|
||||||
|
import com.arkivanov.decompose.router.slot.childSlot
|
||||||
|
import com.tangem.core.decompose.context.AppComponentContext
|
||||||
|
import com.tangem.core.decompose.model.getOrCreateModel
|
||||||
|
import com.tangem.core.ui.decompose.ComposableBottomSheetComponent
|
||||||
|
import com.tangem.features.managetokens.choosetoken.entity.ChooseManageTokensBottomSheetConfig
|
||||||
|
import com.tangem.features.managetokens.choosetoken.model.ChooseManagedTokensModel
|
||||||
|
import com.tangem.features.managetokens.choosetoken.ui.ChooseManagedTokenContent
|
||||||
|
import com.tangem.features.managetokens.component.ChooseManagedTokensComponent
|
||||||
|
import dagger.assisted.Assisted
|
||||||
|
import dagger.assisted.AssistedFactory
|
||||||
|
import dagger.assisted.AssistedInject
|
||||||
|
|
||||||
|
internal class DefaultChooseManagedTokensComponent @AssistedInject constructor(
|
||||||
|
@Assisted context: AppComponentContext,
|
||||||
|
@Assisted private val params: ChooseManagedTokensComponent.Params,
|
||||||
|
) : ChooseManagedTokensComponent, AppComponentContext by context {
|
||||||
|
|
||||||
|
private val model: ChooseManagedTokensModel = getOrCreateModel(params)
|
||||||
|
|
||||||
|
private val bottomSheetSlot = childSlot(
|
||||||
|
source = model.bottomSheetNavigation,
|
||||||
|
serializer = ChooseManageTokensBottomSheetConfig.serializer(),
|
||||||
|
handleBackButton = false,
|
||||||
|
childFactory = ::bottomSheetChild,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
override fun Content(modifier: Modifier) {
|
||||||
|
val uiState by model.uiState.collectAsStateWithLifecycle()
|
||||||
|
val bottomSheet by bottomSheetSlot.subscribeAsState()
|
||||||
|
|
||||||
|
ChooseManagedTokenContent(
|
||||||
|
state = uiState,
|
||||||
|
)
|
||||||
|
bottomSheet.child?.instance?.BottomSheet()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("UnusedPrivateMember")
|
||||||
|
private fun bottomSheetChild(
|
||||||
|
config: ChooseManageTokensBottomSheetConfig,
|
||||||
|
componentContext: ComponentContext,
|
||||||
|
): ComposableBottomSheetComponent = when (config) {
|
||||||
|
else -> getStubComponent()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getStubComponent() = StubComponent()
|
||||||
|
|
||||||
|
class StubComponent : ComposableBottomSheetComponent {
|
||||||
|
override fun dismiss() {}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
override fun BottomSheet() {
|
||||||
|
/* no-op */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@AssistedFactory
|
||||||
|
interface Factory : ChooseManagedTokensComponent.Factory {
|
||||||
|
override fun create(
|
||||||
|
context: AppComponentContext,
|
||||||
|
params: ChooseManagedTokensComponent.Params,
|
||||||
|
): DefaultChooseManagedTokensComponent
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,35 @@
|
||||||
|
package com.tangem.features.managetokens.choosetoken.di
|
||||||
|
|
||||||
|
import com.tangem.core.decompose.di.ModelComponent
|
||||||
|
import com.tangem.core.decompose.model.Model
|
||||||
|
import com.tangem.features.managetokens.choosetoken.DefaultChooseManagedTokensComponent
|
||||||
|
import com.tangem.features.managetokens.choosetoken.model.ChooseManagedTokensModel
|
||||||
|
import com.tangem.features.managetokens.component.ChooseManagedTokensComponent
|
||||||
|
import dagger.Binds
|
||||||
|
import dagger.Module
|
||||||
|
import dagger.hilt.InstallIn
|
||||||
|
import dagger.hilt.components.SingletonComponent
|
||||||
|
import dagger.multibindings.ClassKey
|
||||||
|
import dagger.multibindings.IntoMap
|
||||||
|
import javax.inject.Singleton
|
||||||
|
|
||||||
|
@InstallIn(ModelComponent::class)
|
||||||
|
@Module
|
||||||
|
internal interface ChooseManagedTokensModule {
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
@IntoMap
|
||||||
|
@ClassKey(ChooseManagedTokensModel::class)
|
||||||
|
fun provideChooseManagedTokensModel(impl: ChooseManagedTokensModel): Model
|
||||||
|
}
|
||||||
|
|
||||||
|
@Module
|
||||||
|
@InstallIn(SingletonComponent::class)
|
||||||
|
internal interface ChooseManagedTokensModuleBinds {
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
@Singleton
|
||||||
|
fun provideChooseManagedTokensComponent(
|
||||||
|
impl: DefaultChooseManagedTokensComponent.Factory,
|
||||||
|
): ChooseManagedTokensComponent.Factory
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
package com.tangem.features.managetokens.choosetoken.entity
|
||||||
|
|
||||||
|
import com.tangem.domain.managetokens.model.ManagedCryptoCurrency
|
||||||
|
import com.tangem.domain.models.currency.CryptoCurrency
|
||||||
|
import com.tangem.domain.wallets.models.UserWalletId
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
internal sealed class ChooseManageTokensBottomSheetConfig {
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class SwapTokensBottomSheetConfig(
|
||||||
|
val userWalletId: UserWalletId,
|
||||||
|
val initialCurrency: CryptoCurrency,
|
||||||
|
val token: ManagedCryptoCurrency.Token,
|
||||||
|
) : ChooseManageTokensBottomSheetConfig()
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
package com.tangem.features.managetokens.choosetoken.entity
|
||||||
|
|
||||||
|
import com.tangem.common.ui.notifications.NotificationUM
|
||||||
|
import com.tangem.features.managetokens.entity.managetokens.ManageTokensUM
|
||||||
|
|
||||||
|
internal data class ChooseManagedTokenUM(
|
||||||
|
val notificationUM: NotificationUM?,
|
||||||
|
val readContent: ManageTokensUM.ReadContent,
|
||||||
|
)
|
||||||
|
|
@ -0,0 +1,270 @@
|
||||||
|
package com.tangem.features.managetokens.choosetoken.model
|
||||||
|
|
||||||
|
import androidx.annotation.StringRes
|
||||||
|
import com.arkivanov.decompose.router.slot.SlotNavigation
|
||||||
|
import com.arkivanov.decompose.router.slot.activate
|
||||||
|
import com.tangem.common.ui.notifications.NotificationUM
|
||||||
|
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.decompose.navigation.Router
|
||||||
|
import com.tangem.core.decompose.ui.UiMessageSender
|
||||||
|
import com.tangem.core.ui.components.fields.entity.SearchBarUM
|
||||||
|
import com.tangem.core.ui.event.consumedEvent
|
||||||
|
import com.tangem.core.ui.event.triggeredEvent
|
||||||
|
import com.tangem.core.ui.extensions.resourceReference
|
||||||
|
import com.tangem.core.ui.extensions.stringReference
|
||||||
|
import com.tangem.core.ui.message.SnackbarMessage
|
||||||
|
import com.tangem.features.managetokens.choosetoken.entity.ChooseManageTokensBottomSheetConfig
|
||||||
|
import com.tangem.features.managetokens.choosetoken.entity.ChooseManagedTokenUM
|
||||||
|
import com.tangem.features.managetokens.component.ChooseManagedTokensComponent
|
||||||
|
import com.tangem.features.managetokens.component.ChooseManagedTokensComponent.Source
|
||||||
|
import com.tangem.features.managetokens.component.ManageTokensSource
|
||||||
|
import com.tangem.features.managetokens.entity.item.CurrencyItemUM
|
||||||
|
import com.tangem.features.managetokens.entity.managetokens.ManageTokensTopBarUM
|
||||||
|
import com.tangem.features.managetokens.entity.managetokens.ManageTokensUM
|
||||||
|
import com.tangem.features.managetokens.impl.R
|
||||||
|
import com.tangem.features.managetokens.utils.list.ManageTokensListManager
|
||||||
|
import com.tangem.features.managetokens.utils.list.getLoadingItems
|
||||||
|
import com.tangem.pagination.BatchFetchResult
|
||||||
|
import com.tangem.pagination.PaginationStatus
|
||||||
|
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||||
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
|
import kotlinx.coroutines.FlowPreview
|
||||||
|
import kotlinx.coroutines.flow.*
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import timber.log.Timber
|
||||||
|
import javax.inject.Inject
|
||||||
|
import kotlin.collections.isNotEmpty
|
||||||
|
|
||||||
|
@ModelScoped
|
||||||
|
internal class ChooseManagedTokensModel @Inject constructor(
|
||||||
|
private val router: Router,
|
||||||
|
override val dispatchers: CoroutineDispatcherProvider,
|
||||||
|
private val uiMessageSender: UiMessageSender,
|
||||||
|
paramsContainer: ParamsContainer,
|
||||||
|
manageTokensListManagerFactory: ManageTokensListManager.Factory,
|
||||||
|
) : Model() {
|
||||||
|
|
||||||
|
private val params: ChooseManagedTokensComponent.Params = paramsContainer.require()
|
||||||
|
|
||||||
|
private val manageTokensListManager = manageTokensListManagerFactory.create(
|
||||||
|
onCurrencySelect = { token ->
|
||||||
|
bottomSheetNavigation.activate(
|
||||||
|
ChooseManageTokensBottomSheetConfig.SwapTokensBottomSheetConfig(
|
||||||
|
userWalletId = params.userWalletId,
|
||||||
|
initialCurrency = params.initialCurrency,
|
||||||
|
token = token,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
val bottomSheetNavigation: SlotNavigation<ChooseManageTokensBottomSheetConfig> = SlotNavigation()
|
||||||
|
val uiState: StateFlow<ChooseManagedTokenUM>
|
||||||
|
field = MutableStateFlow<ChooseManagedTokenUM>(createReadContentModel())
|
||||||
|
|
||||||
|
init {
|
||||||
|
manageTokensListManager.uiItems
|
||||||
|
.onEach { items -> updateItems(items) }
|
||||||
|
.launchIn(modelScope)
|
||||||
|
|
||||||
|
manageTokensListManager.paginationStatus
|
||||||
|
.onEach { status -> updatePaginationStatus(status) }
|
||||||
|
.launchIn(modelScope)
|
||||||
|
|
||||||
|
observeSearchQueryChanges()
|
||||||
|
|
||||||
|
modelScope.launch {
|
||||||
|
manageTokensListManager.launchPagination(source = ManageTokensSource.SEND_VIA_SWAP, userWalletId = null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createReadContentModel(): ChooseManagedTokenUM {
|
||||||
|
return ChooseManagedTokenUM(
|
||||||
|
notificationUM = getNotification(),
|
||||||
|
readContent = ManageTokensUM.ReadContent(
|
||||||
|
popBack = router::pop,
|
||||||
|
isInitialBatchLoading = true,
|
||||||
|
isNextBatchLoading = false,
|
||||||
|
items = getLoadingItems(),
|
||||||
|
topBar = ManageTokensTopBarUM.ReadContent(
|
||||||
|
title = resourceReference(R.string.common_choose_token),
|
||||||
|
onBackButtonClick = router::pop,
|
||||||
|
),
|
||||||
|
search = SearchBarUM(
|
||||||
|
placeholderText = resourceReference(R.string.common_search),
|
||||||
|
query = "",
|
||||||
|
onQueryChange = ::searchCurrencies,
|
||||||
|
isActive = false,
|
||||||
|
onActiveChange = ::toggleSearchBar,
|
||||||
|
),
|
||||||
|
loadMore = ::loadMoreItems,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getNotification(): NotificationUM? {
|
||||||
|
return when (params.source) {
|
||||||
|
Source.SendViaSwap -> ChooseManagedTokensNotificationUM.SendViaSwap(
|
||||||
|
onCloseClick = ::removeNotification,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun removeNotification() {
|
||||||
|
uiState.update {
|
||||||
|
it.copy(
|
||||||
|
notificationUM = null,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@OptIn(FlowPreview::class)
|
||||||
|
private fun observeSearchQueryChanges() {
|
||||||
|
uiState
|
||||||
|
.distinctUntilChanged { old, new ->
|
||||||
|
// It's also used to skip search activation to avoid searching an empty query
|
||||||
|
old.readContent.search.query == new.readContent.search.query && new.readContent.search.isActive
|
||||||
|
}
|
||||||
|
.transform { state ->
|
||||||
|
val query = state.readContent.search.query
|
||||||
|
|
||||||
|
if (state.readContent.search.isActive) {
|
||||||
|
emit(query)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.sample(periodMillis = 1_000)
|
||||||
|
.onEach { query -> manageTokensListManager.search(userWalletId = null, query = query) }
|
||||||
|
.launchIn(modelScope)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateItems(items: ImmutableList<CurrencyItemUM>) {
|
||||||
|
uiState.update { state ->
|
||||||
|
state.copy(
|
||||||
|
readContent = state.readContent.copy(items = items),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun consumeScrollToTopEvent() {
|
||||||
|
uiState.update { state ->
|
||||||
|
state.copy(
|
||||||
|
readContent = state.readContent.copy(scrollToTop = consumedEvent()),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updatePaginationStatus(status: PaginationStatus<*>) {
|
||||||
|
uiState.update { state ->
|
||||||
|
val readContent = state.readContent
|
||||||
|
when (status) {
|
||||||
|
is PaginationStatus.None,
|
||||||
|
is PaginationStatus.InitialLoading,
|
||||||
|
-> {
|
||||||
|
if (readContent.search.isActive) {
|
||||||
|
state.copy(
|
||||||
|
readContent = readContent.copy(items = getLoadingItems()),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
state.copy(
|
||||||
|
readContent = readContent.copy(
|
||||||
|
items = getLoadingItems(),
|
||||||
|
isInitialBatchLoading = true,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is PaginationStatus.NextBatchLoading -> state.copy(
|
||||||
|
readContent = readContent.copy(isNextBatchLoading = true),
|
||||||
|
)
|
||||||
|
is PaginationStatus.InitialLoadingError -> {
|
||||||
|
val message = SnackbarMessage(
|
||||||
|
message = status.throwable.localizedMessage
|
||||||
|
?.let(::stringReference)
|
||||||
|
?: resourceReference(R.string.common_error),
|
||||||
|
)
|
||||||
|
uiMessageSender.send(message)
|
||||||
|
|
||||||
|
state.copy(
|
||||||
|
readContent = readContent.copy(
|
||||||
|
isInitialBatchLoading = false,
|
||||||
|
isNextBatchLoading = false,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
is PaginationStatus.Paginating -> {
|
||||||
|
(status.lastResult as? BatchFetchResult.Error)?.let { fetchError ->
|
||||||
|
Timber.e(fetchError.throwable)
|
||||||
|
}
|
||||||
|
|
||||||
|
state.copy(
|
||||||
|
readContent = readContent.copy(
|
||||||
|
isInitialBatchLoading = false,
|
||||||
|
isNextBatchLoading = false,
|
||||||
|
scrollToTop = if (readContent.isInitialBatchLoading && readContent.items.isNotEmpty()) {
|
||||||
|
triggeredEvent(
|
||||||
|
data = Unit,
|
||||||
|
onConsume = ::consumeScrollToTopEvent,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
readContent.scrollToTop
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
is PaginationStatus.EndOfPagination -> {
|
||||||
|
state.copy(
|
||||||
|
readContent = readContent.copy(
|
||||||
|
isInitialBatchLoading = false,
|
||||||
|
isNextBatchLoading = false,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun searchCurrencies(query: String) {
|
||||||
|
uiState.update { state ->
|
||||||
|
state.copy(
|
||||||
|
readContent = state.readContent.copy(
|
||||||
|
search = state.readContent.search.copy(
|
||||||
|
query = query,
|
||||||
|
isActive = true,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun toggleSearchBar(isActive: Boolean) {
|
||||||
|
uiState.update { state ->
|
||||||
|
@StringRes val placeholderTextRes = if (isActive) {
|
||||||
|
R.string.manage_tokens_search_placeholder
|
||||||
|
} else {
|
||||||
|
R.string.common_search
|
||||||
|
}
|
||||||
|
|
||||||
|
state.copy(
|
||||||
|
readContent = state.readContent.copy(
|
||||||
|
search = state.readContent.search.copy(
|
||||||
|
placeholderText = resourceReference(placeholderTextRes),
|
||||||
|
isActive = isActive,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun loadMoreItems(): Boolean {
|
||||||
|
val state = uiState.value
|
||||||
|
if (state.readContent.isInitialBatchLoading || state.readContent.isNextBatchLoading) return false
|
||||||
|
|
||||||
|
modelScope.launch {
|
||||||
|
manageTokensListManager.loadMore(userWalletId = null, query = state.readContent.search.query)
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
package com.tangem.features.managetokens.choosetoken.model
|
||||||
|
|
||||||
|
import com.tangem.common.ui.notifications.NotificationUM
|
||||||
|
import com.tangem.core.ui.extensions.resourceReference
|
||||||
|
import com.tangem.features.managetokens.impl.R
|
||||||
|
|
||||||
|
internal object ChooseManagedTokensNotificationUM {
|
||||||
|
|
||||||
|
data class SendViaSwap(
|
||||||
|
val onCloseClick: () -> Unit,
|
||||||
|
) : NotificationUM.Info(
|
||||||
|
title = resourceReference(R.string.send_with_swap_title),
|
||||||
|
subtitle = resourceReference(R.string.send_with_swap_notification_text),
|
||||||
|
iconResId = R.drawable.ic_exchange_horizontal_24,
|
||||||
|
onCloseClick = onCloseClick,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,234 @@
|
||||||
|
package com.tangem.features.managetokens.choosetoken.ui
|
||||||
|
|
||||||
|
import android.content.res.Configuration
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.layout.*
|
||||||
|
import androidx.compose.foundation.lazy.*
|
||||||
|
import androidx.compose.material3.Scaffold
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.platform.LocalDensity
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import com.tangem.common.ui.notifications.NotificationUM
|
||||||
|
import com.tangem.core.ui.components.currency.icon.CurrencyIconState
|
||||||
|
import com.tangem.core.ui.components.fields.entity.SearchBarUM
|
||||||
|
import com.tangem.core.ui.components.list.InfiniteListHandler
|
||||||
|
import com.tangem.core.ui.components.notifications.Notification
|
||||||
|
import com.tangem.core.ui.components.rows.ChainRow
|
||||||
|
import com.tangem.core.ui.components.rows.model.ChainRowUM
|
||||||
|
import com.tangem.core.ui.decorations.roundedShapeItemDecoration
|
||||||
|
import com.tangem.core.ui.event.EventEffect
|
||||||
|
import com.tangem.core.ui.extensions.resourceReference
|
||||||
|
import com.tangem.core.ui.res.TangemTheme
|
||||||
|
import com.tangem.core.ui.res.TangemThemePreview
|
||||||
|
import com.tangem.core.ui.utils.WindowInsetsZero
|
||||||
|
import com.tangem.domain.managetokens.model.ManagedCryptoCurrency
|
||||||
|
import com.tangem.features.managetokens.choosetoken.entity.ChooseManagedTokenUM
|
||||||
|
import com.tangem.features.managetokens.choosetoken.model.ChooseManagedTokensNotificationUM
|
||||||
|
import com.tangem.features.managetokens.entity.item.CurrencyItemUM
|
||||||
|
import com.tangem.features.managetokens.entity.managetokens.ManageTokensTopBarUM
|
||||||
|
import com.tangem.features.managetokens.entity.managetokens.ManageTokensUM
|
||||||
|
import com.tangem.features.managetokens.impl.R
|
||||||
|
import com.tangem.features.managetokens.ui.*
|
||||||
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
|
import kotlinx.collections.immutable.toPersistentList
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
internal fun ChooseManagedTokenContent(state: ChooseManagedTokenUM, modifier: Modifier = Modifier) {
|
||||||
|
Scaffold(
|
||||||
|
modifier = modifier,
|
||||||
|
containerColor = TangemTheme.colors.background.tertiary,
|
||||||
|
contentWindowInsets = WindowInsetsZero,
|
||||||
|
topBar = {
|
||||||
|
ManageTokensTopBar(
|
||||||
|
modifier = Modifier.statusBarsPadding(),
|
||||||
|
topBar = state.readContent.topBar,
|
||||||
|
search = state.readContent.search,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
content = { innerPadding ->
|
||||||
|
Content(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(innerPadding)
|
||||||
|
.fillMaxSize(),
|
||||||
|
state = state,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun Content(state: ChooseManagedTokenUM, modifier: Modifier = Modifier) {
|
||||||
|
val listState = rememberLazyListState()
|
||||||
|
|
||||||
|
Box(modifier = modifier) {
|
||||||
|
Currencies(
|
||||||
|
modifier = Modifier.fillMaxSize(),
|
||||||
|
listState = listState,
|
||||||
|
notificationUM = state.notificationUM,
|
||||||
|
items = state.readContent.items,
|
||||||
|
showLoadingItem = state.readContent.isNextBatchLoading,
|
||||||
|
onLoadMore = state.readContent.loadMore,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
EventEffect(event = state.readContent.scrollToTop) {
|
||||||
|
listState.animateScrollToItem(index = 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun Currencies(
|
||||||
|
listState: LazyListState,
|
||||||
|
notificationUM: NotificationUM?,
|
||||||
|
items: ImmutableList<CurrencyItemUM>,
|
||||||
|
showLoadingItem: Boolean,
|
||||||
|
onLoadMore: () -> Boolean,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
) {
|
||||||
|
val bottomBarHeight = with(LocalDensity.current) {
|
||||||
|
WindowInsets.systemBars.getBottom(density = this).toDp()
|
||||||
|
}
|
||||||
|
|
||||||
|
LazyColumn(
|
||||||
|
modifier = modifier.padding(horizontal = 16.dp),
|
||||||
|
state = listState,
|
||||||
|
contentPadding = PaddingValues(
|
||||||
|
bottom = TangemTheme.dimens.spacing76 + bottomBarHeight,
|
||||||
|
),
|
||||||
|
) {
|
||||||
|
if (notificationUM?.config != null) {
|
||||||
|
item(key = "notification_key") {
|
||||||
|
Notification(
|
||||||
|
config = notificationUM.config,
|
||||||
|
iconTint = TangemTheme.colors.icon.accent,
|
||||||
|
modifier = Modifier
|
||||||
|
.animateItem()
|
||||||
|
.padding(bottom = 12.dp),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
contentItems(items)
|
||||||
|
|
||||||
|
if (showLoadingItem) {
|
||||||
|
item(key = "loading_item") {
|
||||||
|
ProgressIndicator(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(vertical = TangemTheme.dimens.spacing16)
|
||||||
|
.fillMaxWidth(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
InfiniteListHandler(
|
||||||
|
listState = listState,
|
||||||
|
buffer = LOAD_ITEMS_BUFFER,
|
||||||
|
onLoadMore = onLoadMore,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun LazyListScope.contentItems(items: ImmutableList<CurrencyItemUM>) {
|
||||||
|
itemsIndexed(
|
||||||
|
items = items,
|
||||||
|
key = { index, item -> item.id.value },
|
||||||
|
) { index, item ->
|
||||||
|
when (item) {
|
||||||
|
is CurrencyItemUM.Basic -> {
|
||||||
|
ChainRow(
|
||||||
|
model = with(item) {
|
||||||
|
ChainRowUM(
|
||||||
|
name = name,
|
||||||
|
type = symbol,
|
||||||
|
icon = icon,
|
||||||
|
showCustom = false,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
modifier = Modifier
|
||||||
|
.roundedShapeItemDecoration(
|
||||||
|
currentIndex = index,
|
||||||
|
addDefaultPadding = false,
|
||||||
|
lastIndex = items.lastIndex,
|
||||||
|
)
|
||||||
|
.clickable(onClick = item.onExpandClick)
|
||||||
|
.background(TangemTheme.colors.background.action),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
is CurrencyItemUM.Loading -> {
|
||||||
|
LoadingItem(
|
||||||
|
modifier = Modifier
|
||||||
|
.roundedShapeItemDecoration(
|
||||||
|
currentIndex = index,
|
||||||
|
addDefaultPadding = false,
|
||||||
|
lastIndex = items.lastIndex,
|
||||||
|
)
|
||||||
|
.fillMaxWidth()
|
||||||
|
.background(TangemTheme.colors.background.action),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
is CurrencyItemUM.SearchNothingFound -> {
|
||||||
|
SearchNothingFoundText(
|
||||||
|
modifier = Modifier
|
||||||
|
.roundedShapeItemDecoration(
|
||||||
|
currentIndex = index,
|
||||||
|
addDefaultPadding = false,
|
||||||
|
lastIndex = items.lastIndex,
|
||||||
|
)
|
||||||
|
.background(TangemTheme.colors.background.action)
|
||||||
|
.fillParentMaxSize(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
else -> Unit
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// region Preview
|
||||||
|
@Composable
|
||||||
|
@Preview(showBackground = true, widthDp = 360)
|
||||||
|
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||||
|
private fun ChooseManagedTokenContent_Preview() {
|
||||||
|
TangemThemePreview {
|
||||||
|
ChooseManagedTokenContent(
|
||||||
|
state = ChooseManagedTokenUM(
|
||||||
|
notificationUM = ChooseManagedTokensNotificationUM.SendViaSwap({}),
|
||||||
|
readContent = ManageTokensUM.ReadContent(
|
||||||
|
popBack = {},
|
||||||
|
isInitialBatchLoading = false,
|
||||||
|
isNextBatchLoading = false,
|
||||||
|
items = buildList {
|
||||||
|
repeat(10) {
|
||||||
|
add(
|
||||||
|
CurrencyItemUM.Basic(
|
||||||
|
id = ManagedCryptoCurrency.ID(
|
||||||
|
value = "ID+$it",
|
||||||
|
),
|
||||||
|
name = "Bitcoin",
|
||||||
|
symbol = "BTC",
|
||||||
|
icon = CurrencyIconState.Loading,
|
||||||
|
networks = CurrencyItemUM.Basic.NetworksUM.Collapsed,
|
||||||
|
onExpandClick = {},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}.toPersistentList(),
|
||||||
|
topBar = ManageTokensTopBarUM.ReadContent(
|
||||||
|
title = resourceReference(R.string.common_choose_token),
|
||||||
|
onBackButtonClick = {},
|
||||||
|
),
|
||||||
|
search = SearchBarUM(
|
||||||
|
placeholderText = resourceReference(R.string.common_search),
|
||||||
|
query = "",
|
||||||
|
onQueryChange = {},
|
||||||
|
isActive = false,
|
||||||
|
onActiveChange = {},
|
||||||
|
),
|
||||||
|
loadMore = { true },
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// endregion
|
||||||
Loading…
Add table
Add a link
Reference in a new issue