Updated on 2026-08-14

This commit is contained in:
Tangem 2024-10-28 16:52:05 +04:00
parent 41131e5b73
commit f5eca5ab2a
23 changed files with 160 additions and 138 deletions

View file

@ -1,46 +0,0 @@
package com.tangem.feature.wallet.presentation.selecttoken
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import com.tangem.core.decompose.context.AppComponentContext
import com.tangem.core.decompose.context.child
import com.tangem.feature.wallet.presentation.selecttoken.ui.SelectToken
import com.tangem.feature.wallet.presentation.tokenlist.TokenListComponent
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
internal class DefaultSelectTokenComponent @AssistedInject constructor(
@Assisted appComponentContext: AppComponentContext,
tokenListComponentFactory: TokenListComponent.Factory,
@Assisted private val params: SelectTokenComponent.Params,
) : AppComponentContext by appComponentContext, SelectTokenComponent {
private val tokenListComponent: TokenListComponent = tokenListComponentFactory.create(
context = child(key = "token_list"),
params = TokenListComponent.Params(
hasSearchBar = params.hasSearchBar,
userWalletId = params.userWalletId,
onTokenClick = params.onTokenClick,
),
)
@Composable
override fun Content(modifier: Modifier) {
SelectToken(
titleResId = params.titleResId,
onBackClick = router::pop,
tokenListComponent = tokenListComponent,
modifier = modifier,
)
}
@AssistedFactory
interface Factory : SelectTokenComponent.Factory {
override fun create(
context: AppComponentContext,
params: SelectTokenComponent.Params,
): DefaultSelectTokenComponent
}
}

View file

@ -1,32 +0,0 @@
package com.tangem.feature.wallet.presentation.selecttoken
import androidx.annotation.StringRes
import com.tangem.core.decompose.factory.ComponentFactory
import com.tangem.core.ui.decompose.ComposableContentComponent
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.domain.wallets.models.UserWalletId
/**
* Select token component
*
[REDACTED_AUTHOR]
*/
interface SelectTokenComponent : ComposableContentComponent {
interface Factory : ComponentFactory<Params, SelectTokenComponent>
/**
* Params
*
* @property hasSearchBar flag that indicates if search bar should be shown
* @property userWalletId id of multi-currency wallet
* @property titleResId resource id of title
* @property onTokenClick callback for token click
*/
data class Params(
val hasSearchBar: Boolean,
val userWalletId: UserWalletId,
@StringRes val titleResId: Int,
val onTokenClick: (CryptoCurrencyStatus) -> Unit,
)
}

View file

@ -1,18 +0,0 @@
package com.tangem.feature.wallet.presentation.selecttoken.di
import com.tangem.feature.wallet.presentation.selecttoken.DefaultSelectTokenComponent
import com.tangem.feature.wallet.presentation.selecttoken.SelectTokenComponent
import dagger.Binds
import dagger.Module
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton
@Module
@InstallIn(SingletonComponent::class)
internal interface SelectTokenComponentModule {
@Binds
@Singleton
fun bindSelectTokenComponentFactory(factory: DefaultSelectTokenComponent.Factory): SelectTokenComponent.Factory
}

View file

@ -1,45 +0,0 @@
package com.tangem.feature.wallet.presentation.selecttoken.ui
import androidx.activity.compose.BackHandler
import androidx.annotation.StringRes
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.imePadding
import androidx.compose.foundation.layout.systemBarsPadding
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import com.tangem.core.ui.components.appbar.AppBarWithBackButton
import com.tangem.core.ui.res.TangemTheme
import com.tangem.feature.wallet.impl.R
import com.tangem.feature.wallet.presentation.tokenlist.TokenListComponent
@Composable
internal fun SelectToken(
@StringRes titleResId: Int,
onBackClick: () -> Unit,
tokenListComponent: TokenListComponent,
modifier: Modifier = Modifier,
) {
BackHandler(onBack = onBackClick)
Column(
modifier = modifier
.background(TangemTheme.colors.background.secondary)
.imePadding()
.systemBarsPadding(),
) {
AppBarWithBackButton(
onBackClick = onBackClick,
text = stringResource(id = titleResId),
iconRes = R.drawable.ic_close_24,
)
tokenListComponent.Content(
contentPadding = PaddingValues(vertical = 8.dp, horizontal = 16.dp),
modifier = Modifier,
)
}
}

View file

@ -1,36 +0,0 @@
package com.tangem.feature.wallet.presentation.tokenlist
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Stable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.tangem.core.decompose.context.AppComponentContext
import com.tangem.core.decompose.model.getOrCreateModel
import com.tangem.feature.wallet.presentation.tokenlist.model.TokenListModel
import com.tangem.feature.wallet.presentation.tokenlist.ui.TokenList
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
@Stable
internal class DefaultTokenListComponent @AssistedInject constructor(
@Assisted context: AppComponentContext,
@Assisted params: TokenListComponent.Params,
) : TokenListComponent, AppComponentContext by context {
private val model: TokenListModel = getOrCreateModel(params)
@Composable
override fun Content(contentPadding: PaddingValues, modifier: Modifier) {
val state by model.state.collectAsStateWithLifecycle()
TokenList(state = state, contentPadding = contentPadding, modifier = modifier)
}
@AssistedFactory
interface Factory : TokenListComponent.Factory {
override fun create(context: AppComponentContext, params: TokenListComponent.Params): DefaultTokenListComponent
}
}

View file

@ -1,33 +0,0 @@
package com.tangem.feature.wallet.presentation.tokenlist
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Stable
import androidx.compose.ui.Modifier
import com.tangem.core.decompose.factory.ComponentFactory
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.domain.wallets.models.UserWalletId
/** Token list component that present list of token for multi-currency wallet */
@Stable
internal interface TokenListComponent {
@Composable
fun Content(contentPadding: PaddingValues, modifier: Modifier)
/** Component factory */
interface Factory : ComponentFactory<Params, TokenListComponent>
/**
* Params
*
* @property hasSearchBar flag that indicates if search bar should be shown
* @property userWalletId id of multi-currency wallet
* @property onTokenClick callback for token click
*/
data class Params(
val hasSearchBar: Boolean,
val userWalletId: UserWalletId,
val onTokenClick: (CryptoCurrencyStatus) -> Unit,
)
}

View file

@ -1,18 +0,0 @@
package com.tangem.feature.wallet.presentation.tokenlist.di
import com.tangem.feature.wallet.presentation.tokenlist.DefaultTokenListComponent
import com.tangem.feature.wallet.presentation.tokenlist.TokenListComponent
import dagger.Binds
import dagger.Module
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton
@Module
@InstallIn(SingletonComponent::class)
internal interface TokenListComponentModule {
@Binds
@Singleton
fun bindTokenListComponentFactory(factory: DefaultTokenListComponent.Factory): TokenListComponent.Factory
}

View file

@ -1,20 +0,0 @@
package com.tangem.feature.wallet.presentation.tokenlist.di
import com.tangem.core.decompose.di.DecomposeComponent
import com.tangem.core.decompose.model.Model
import com.tangem.feature.wallet.presentation.tokenlist.model.TokenListModel
import dagger.Binds
import dagger.Module
import dagger.hilt.InstallIn
import dagger.multibindings.ClassKey
import dagger.multibindings.IntoMap
@Module
@InstallIn(DecomposeComponent::class)
internal interface TokenListModelModule {
@Binds
@IntoMap
@ClassKey(TokenListModel::class)
fun provideTokenListModel(model: TokenListModel): Model
}

View file

@ -1,35 +0,0 @@
package com.tangem.feature.wallet.presentation.tokenlist.entity
import com.tangem.core.ui.components.tokenlist.state.TokensListItemUM
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
/**
* Token list UM
*
* @property items items (search bar, tokens, headers)
* @property isBalanceHidden flag that indicates if balance should be hidden
*
[REDACTED_AUTHOR]
*/
internal data class TokenListUM(
val items: ImmutableList<TokensListItemUM>,
val isBalanceHidden: Boolean,
) {
/** Get search bar if it exists */
fun getSearchBar(): TokensListItemUM.SearchBar? {
return items.firstOrNull() as? TokensListItemUM.SearchBar
}
/** Get tokens */
fun getTokens(): ImmutableList<TokensListItemUM> {
if (getSearchBar() == null) return items
return if (items.size > 1) {
items.subList(fromIndex = 1, toIndex = items.size)
} else {
persistentListOf()
}
}
}

View file

@ -1,41 +0,0 @@
package com.tangem.feature.wallet.presentation.tokenlist.entity
import com.tangem.core.ui.components.tokenlist.state.TokensListItemUM
import kotlinx.collections.immutable.persistentListOf
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.update
import timber.log.Timber
import javax.inject.Inject
/**
* [TokenListUM] controller
*
[REDACTED_AUTHOR]
*/
internal class TokenListUMController @Inject constructor() {
val state: StateFlow<TokenListUM> get() = _state
private val _state: MutableStateFlow<TokenListUM> = MutableStateFlow(
value = TokenListUM(
items = persistentListOf(),
isBalanceHidden = false,
),
)
fun update(transform: (TokenListUM) -> TokenListUM) {
Timber.d("Applying non-name transformation")
_state.update(transform)
}
fun update(transformer: TokenListUMTransformer) {
Timber.d("Applying ${transformer::class.simpleName}")
_state.update(transformer::transform)
}
/** Get search bar if it exists */
fun getSearchBar(): TokensListItemUM.SearchBar? {
return _state.value.getSearchBar()
}
}

View file

@ -1,10 +0,0 @@
package com.tangem.feature.wallet.presentation.tokenlist.entity
import com.tangem.utils.transformer.Transformer
/**
* Base [TokenListUM] transformer
*
[REDACTED_AUTHOR]
*/
internal interface TokenListUMTransformer : Transformer<TokenListUM>

View file

@ -1,33 +0,0 @@
package com.tangem.feature.wallet.presentation.tokenlist.entity.transformer
import com.tangem.core.ui.components.fields.entity.SearchBarUM
import com.tangem.feature.wallet.presentation.tokenlist.entity.TokenListUM
import com.tangem.feature.wallet.presentation.tokenlist.entity.TokenListUMTransformer
import kotlinx.collections.immutable.persistentListOf
/**
* Base [SearchBarUM] transformer
*
[REDACTED_AUTHOR]
*/
internal abstract class SearchBarUMTransformer : TokenListUMTransformer {
abstract fun transform(prevState: SearchBarUM): SearchBarUM
override fun transform(prevState: TokenListUM): TokenListUM {
val searchBarItem = prevState.getSearchBar()
return if (searchBarItem != null) {
val updatedSearchBar = searchBarItem.copy(searchBarUM = transform(searchBarItem.searchBarUM))
prevState.copy(
items = persistentListOf(
updatedSearchBar,
*prevState.getTokens().toTypedArray(),
),
)
} else {
prevState
}
}
}

View file

@ -1,19 +0,0 @@
package com.tangem.feature.wallet.presentation.tokenlist.entity.transformer
import com.tangem.core.ui.components.fields.entity.SearchBarUM
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.feature.wallet.impl.R
internal class UpdateSearchBarActiveStateTransformer(private val isActive: Boolean) : SearchBarUMTransformer() {
override fun transform(prevState: SearchBarUM): SearchBarUM {
val placeholderText = if (isActive) {
TextReference.EMPTY
} else {
resourceReference(id = R.string.common_search)
}
return prevState.copy(placeholderText = placeholderText, isActive = isActive)
}
}

View file

@ -1,10 +0,0 @@
package com.tangem.feature.wallet.presentation.tokenlist.entity.transformer
import com.tangem.core.ui.components.fields.entity.SearchBarUM
internal class UpdateSearchQueryTransformer(private val newQuery: String) : SearchBarUMTransformer() {
override fun transform(prevState: SearchBarUM): SearchBarUM {
return prevState.copy(query = newQuery)
}
}

View file

@ -1,48 +0,0 @@
package com.tangem.feature.wallet.presentation.tokenlist.entity.transformer
import com.tangem.common.ui.tokens.TokenItemStateConverter
import com.tangem.core.ui.components.fields.entity.SearchBarUM
import com.tangem.core.ui.components.tokenlist.state.TokensListItemUM
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.feature.wallet.impl.R
import com.tangem.feature.wallet.presentation.tokenlist.entity.TokenListUM
import com.tangem.feature.wallet.presentation.tokenlist.entity.TokenListUMTransformer
import kotlinx.collections.immutable.toImmutableList
internal class UpdateTokenItemsTransformer(
private val tokenItemStateConverter: TokenItemStateConverter,
private val statuses: List<CryptoCurrencyStatus>,
private val isBalanceHidden: Boolean,
private val hasSearchBar: Boolean,
private val onQueryChange: (String) -> Unit,
private val onActiveChange: (Boolean) -> Unit,
) : TokenListUMTransformer {
override fun transform(prevState: TokenListUM): TokenListUM {
val items = tokenItemStateConverter.convertList(input = statuses).map(TokensListItemUM::Token)
val searchBarItem = if (hasSearchBar) {
prevState.getSearchBar() ?: createSearchBarItem()
} else {
null
}
return prevState.copy(
items = (listOfNotNull(searchBarItem) + items).toImmutableList(),
isBalanceHidden = isBalanceHidden,
)
}
private fun createSearchBarItem(): TokensListItemUM.SearchBar {
return TokensListItemUM.SearchBar(
searchBarUM = SearchBarUM(
placeholderText = resourceReference(id = R.string.common_search),
query = "",
onQueryChange = onQueryChange,
isActive = false,
onActiveChange = onActiveChange,
),
)
}
}

View file

@ -1,105 +0,0 @@
package com.tangem.feature.wallet.presentation.tokenlist.model
import arrow.core.getOrElse
import com.tangem.common.ui.tokens.TokenItemStateConverter
import com.tangem.core.decompose.model.Model
import com.tangem.core.decompose.model.ParamsContainer
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase
import com.tangem.domain.core.utils.getOrElse
import com.tangem.domain.tokens.GetTokenListUseCase
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.domain.tokens.model.TokenList
import com.tangem.feature.wallet.presentation.tokenlist.TokenListComponent
import com.tangem.feature.wallet.presentation.tokenlist.entity.TokenListUM
import com.tangem.feature.wallet.presentation.tokenlist.entity.TokenListUMController
import com.tangem.feature.wallet.presentation.tokenlist.entity.transformer.UpdateSearchBarActiveStateTransformer
import com.tangem.feature.wallet.presentation.tokenlist.entity.transformer.UpdateSearchQueryTransformer
import com.tangem.feature.wallet.presentation.tokenlist.entity.transformer.UpdateTokenItemsTransformer
import com.tangem.feature.wallet.presentation.tokenlist.utils.SearchTokensManager
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch
import javax.inject.Inject
@Suppress("LongParameterList")
internal class TokenListModel @Inject constructor(
paramsContainer: ParamsContainer,
override val dispatchers: CoroutineDispatcherProvider,
private val tokenListUMController: TokenListUMController,
private val searchTokensManager: SearchTokensManager,
private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
private val getBalanceHidingSettingsUseCase: GetBalanceHidingSettingsUseCase,
private val getTokenListUseCase: GetTokenListUseCase,
) : Model() {
val state: StateFlow<TokenListUM> = tokenListUMController.state
private val params: TokenListComponent.Params = paramsContainer.require()
init {
subscribeOnUpdateState()
}
private fun subscribeOnUpdateState() {
combine(
flow = getTokenListUseCase.launch(userWalletId = params.userWalletId).distinctUntilChanged(),
flow2 = getSelectedAppCurrencyUseCase().map { it.getOrElse { AppCurrency.Default } }.distinctUntilChanged(),
flow3 = getBalanceHidingSettingsUseCase().map { it.isBalanceHidden }.distinctUntilChanged(),
flow4 = searchTokensManager.query,
) { maybeTokenList, appCurrency, isBalanceHidden, query ->
val currencies = maybeTokenList.getOrElse(
ifLoading = { it ?: TokenList.Empty },
ifError = { TokenList.Empty },
)
.flattenCurrencies()
val filterTokenList = currencies
.filterByQuery(query = query)
.filterByAvailability()
UpdateTokenItemsTransformer(
tokenItemStateConverter = TokenItemStateConverter(
appCurrency = appCurrency,
onItemClick = params.onTokenClick,
),
statuses = filterTokenList,
isBalanceHidden = isBalanceHidden,
hasSearchBar = params.hasSearchBar && currencies.isNotEmpty(),
onQueryChange = ::onSearchQueryChange,
onActiveChange = ::onSearchBarActiveChange,
)
}
.onEach(tokenListUMController::update)
.flowOn(dispatchers.main)
.launchIn(modelScope)
}
private fun onSearchQueryChange(newQuery: String) {
val searchBar = tokenListUMController.getSearchBar()
if (searchBar?.searchBarUM?.query == newQuery) return
modelScope.launch {
tokenListUMController.update(transformer = UpdateSearchQueryTransformer(newQuery))
searchTokensManager.update(newQuery)
}
}
private fun onSearchBarActiveChange(isActive: Boolean) {
tokenListUMController.update(transformer = UpdateSearchBarActiveStateTransformer(isActive))
}
private fun List<CryptoCurrencyStatus>.filterByQuery(query: String): List<CryptoCurrencyStatus> {
return filter {
it.currency.name.contains(other = query, ignoreCase = true) ||
it.currency.symbol.contains(other = query, ignoreCase = true)
}
}
// TODO: [REDACTED_JIRA]
private fun List<CryptoCurrencyStatus>.filterByAvailability(): List<CryptoCurrencyStatus> {
return this
}
}

View file

@ -1,65 +0,0 @@
package com.tangem.feature.wallet.presentation.tokenlist.ui
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.unit.dp
import com.tangem.core.ui.components.tokenlist.TokenListItem
import com.tangem.core.ui.decorations.roundedShapeItemDecoration
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.feature.wallet.presentation.tokenlist.entity.TokenListUM
import com.tangem.feature.wallet.presentation.tokenlist.ui.preview.PreviewTokenListUMProvider
/**
* Token list
*
* @param state state
* @param modifier modifier
*
[REDACTED_AUTHOR]
*/
@Composable
internal fun TokenList(state: TokenListUM, contentPadding: PaddingValues, modifier: Modifier = Modifier) {
LazyColumn(modifier = modifier, contentPadding = contentPadding) {
itemsIndexed(
items = state.items,
key = { _, item -> item.id },
contentType = { _, item -> item::class.java },
itemContent = { index, item ->
TokenListItem(
state = item,
isBalanceHidden = state.isBalanceHidden,
modifier = Modifier
.animateItem()
.roundedShapeItemDecoration(
currentIndex = index,
lastIndex = state.items.lastIndex,
addDefaultPadding = false,
backgroundColor = TangemTheme.colors.background.primary,
),
)
},
)
}
}
@Preview
@Composable
private fun Preview_TokenList(@PreviewParameter(PreviewTokenListUMProvider::class) state: TokenListUM) {
TangemThemePreview {
TokenList(
state = state,
contentPadding = PaddingValues(all = 16.dp),
modifier = Modifier
.fillMaxWidth()
.background(color = TangemTheme.colors.background.secondary),
)
}
}

View file

@ -1,62 +0,0 @@
package com.tangem.feature.wallet.presentation.tokenlist.ui.preview
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import com.tangem.core.ui.components.currency.icon.CurrencyIconState
import com.tangem.core.ui.components.fields.entity.SearchBarUM
import com.tangem.core.ui.components.marketprice.PriceChangeType
import com.tangem.core.ui.components.token.state.TokenItemState
import com.tangem.core.ui.components.tokenlist.state.TokensListItemUM
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.feature.wallet.impl.R
import com.tangem.feature.wallet.presentation.tokenlist.entity.TokenListUM
import kotlinx.collections.immutable.toImmutableList
internal class PreviewTokenListUMProvider : PreviewParameterProvider<TokenListUM> {
override val values: Sequence<TokenListUM> = sequenceOf(
createTokensList(hasSearchBar = false, createDefaultTokenItem()),
createTokensList(hasSearchBar = true, createDefaultTokenItem()),
)
private fun createTokensList(hasSearchBar: Boolean, vararg items: TokensListItemUM): TokenListUM {
return TokenListUM(
items = buildList {
if (hasSearchBar) {
TokensListItemUM.SearchBar(
searchBarUM = SearchBarUM(
placeholderText = resourceReference(id = R.string.common_search),
query = "",
onQueryChange = {},
isActive = false,
onActiveChange = {},
),
)
.let { add(element = it) }
}
addAll(items)
}
.toImmutableList(),
isBalanceHidden = false,
)
}
private fun createDefaultTokenItem(): TokensListItemUM.Token {
return TokensListItemUM.Token(
state = TokenItemState.Content(
id = "1",
iconState = CurrencyIconState.Locked,
titleState = TokenItemState.TitleState.Content(text = "Bitcoin"),
fiatAmountState = TokenItemState.FiatAmountState.Content(text = "12 368,14 \$"),
subtitle2State = TokenItemState.Subtitle2State.TextContent(text = "0,35853044 BTC"),
subtitleState = TokenItemState.SubtitleState.CryptoPriceContent(
price = "34 496,75 \$",
priceChangePercent = "0,43 %",
type = PriceChangeType.DOWN,
),
onItemClick = {},
onItemLongClick = {},
),
)
}
}

View file

@ -1,33 +0,0 @@
package com.tangem.feature.wallet.presentation.tokenlist.utils
import com.tangem.utils.coroutines.JobHolder
import com.tangem.utils.coroutines.withDebounce
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import javax.inject.Inject
/**
* Search tokens manager
*
[REDACTED_AUTHOR]
*/
internal class SearchTokensManager @Inject constructor() {
val query: Flow<String>
get() = _query
private val _query = MutableStateFlow(value = "")
private val jobHolder = JobHolder()
suspend fun update(value: String) {
coroutineScope {
if (value.isEmpty()) {
_query.value = value
} else {
withDebounce(jobHolder) { _query.value = value }
}
}
}
}