diff --git a/features/manage-tokens/api/src/main/kotlin/com/tangem/features/managetokens/component/ChooseManagedTokensComponent.kt b/features/manage-tokens/api/src/main/kotlin/com/tangem/features/managetokens/component/ChooseManagedTokensComponent.kt new file mode 100644 index 0000000000..0f926c91f3 --- /dev/null +++ b/features/manage-tokens/api/src/main/kotlin/com/tangem/features/managetokens/component/ChooseManagedTokensComponent.kt @@ -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 +} \ No newline at end of file diff --git a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/choosetoken/DefaultChooseManagedTokensComponent.kt b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/choosetoken/DefaultChooseManagedTokensComponent.kt new file mode 100644 index 0000000000..c42147407a --- /dev/null +++ b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/choosetoken/DefaultChooseManagedTokensComponent.kt @@ -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 + } +} \ No newline at end of file diff --git a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/choosetoken/di/ChooseManagedTokensModule.kt b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/choosetoken/di/ChooseManagedTokensModule.kt new file mode 100644 index 0000000000..ff98776324 --- /dev/null +++ b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/choosetoken/di/ChooseManagedTokensModule.kt @@ -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 +} \ No newline at end of file diff --git a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/choosetoken/entity/ChooseManageTokensBottomSheetConfig.kt b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/choosetoken/entity/ChooseManageTokensBottomSheetConfig.kt new file mode 100644 index 0000000000..cd7d30b001 --- /dev/null +++ b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/choosetoken/entity/ChooseManageTokensBottomSheetConfig.kt @@ -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() +} \ No newline at end of file diff --git a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/choosetoken/entity/ChooseManagedTokenUM.kt b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/choosetoken/entity/ChooseManagedTokenUM.kt new file mode 100644 index 0000000000..51b2d222b8 --- /dev/null +++ b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/choosetoken/entity/ChooseManagedTokenUM.kt @@ -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, +) \ No newline at end of file diff --git a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/choosetoken/model/ChooseManagedTokensModel.kt b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/choosetoken/model/ChooseManagedTokensModel.kt new file mode 100644 index 0000000000..65284e752d --- /dev/null +++ b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/choosetoken/model/ChooseManagedTokensModel.kt @@ -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 = SlotNavigation() + val uiState: StateFlow + field = MutableStateFlow(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) { + 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 + } +} \ No newline at end of file diff --git a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/choosetoken/model/ChooseManagedTokensNotificationUM.kt b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/choosetoken/model/ChooseManagedTokensNotificationUM.kt new file mode 100644 index 0000000000..597c9cb888 --- /dev/null +++ b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/choosetoken/model/ChooseManagedTokensNotificationUM.kt @@ -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, + ) +} \ No newline at end of file diff --git a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/choosetoken/ui/ChooseManagedTokenContent.kt b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/choosetoken/ui/ChooseManagedTokenContent.kt new file mode 100644 index 0000000000..632f901ca6 --- /dev/null +++ b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/choosetoken/ui/ChooseManagedTokenContent.kt @@ -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, + 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) { + 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 \ No newline at end of file