Updated on 2026-08-14

This commit is contained in:
Tangem 2024-11-05 15:32:00 +05:00
parent 0be9414914
commit 0fe5355c74
26 changed files with 666 additions and 66 deletions

View file

@ -62,9 +62,9 @@ internal class OnboardingManageTokensModel @Inject constructor(
.launchIn(modelScope)
combine(
manageTokensListManager.currenciesToAdd,
manageTokensListManager.currenciesToRemove,
::handleChangedCurrencies,
flow = manageTokensListManager.currenciesToAdd,
flow2 = manageTokensListManager.currenciesToRemove,
transform = ::handleChangedCurrencies,
).launchIn(modelScope)
observeSearchQueryChanges()

View file

@ -4,12 +4,20 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.arkivanov.decompose.ComponentContext
import com.arkivanov.decompose.extensions.compose.jetpack.subscribeAsState
import com.arkivanov.decompose.router.slot.childSlot
import com.arkivanov.decompose.router.slot.dismiss
import com.tangem.core.decompose.context.AppComponentContext
import com.tangem.core.decompose.context.childByContext
import com.tangem.core.decompose.model.getOrCreateModel
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent
import com.tangem.core.ui.decompose.ComposableBottomSheetComponent
import com.tangem.features.onramp.component.ConfirmResidencyComponent
import com.tangem.features.onramp.model.ConfirmResidencyModel
import com.tangem.features.onramp.selectcountry.SelectCountryComponent
import com.tangem.features.onramp.selectcountry.entity.ConfirmResidencyBottomSheetConfig
import com.tangem.features.onramp.ui.ConfirmResidencyBottomSheet
import com.tangem.features.onramp.ui.ConfirmResidencyBottomSheetContent
import dagger.assisted.Assisted
@ -19,9 +27,16 @@ import dagger.assisted.AssistedInject
internal class DefaultConfirmResidencyComponent @AssistedInject constructor(
@Assisted context: AppComponentContext,
@Assisted private val params: ConfirmResidencyComponent.Params,
private val selectCountryComponentFactory: SelectCountryComponent.Factory,
) : ConfirmResidencyComponent, AppComponentContext by context {
private val model: ConfirmResidencyModel = getOrCreateModel(params)
private val bottomSheetSlot = childSlot(
source = model.bottomSheetNavigation,
serializer = null,
handleBackButton = false,
childFactory = ::bottomSheetChild,
)
override fun dismiss() {
params.onDismiss()
@ -30,6 +45,7 @@ internal class DefaultConfirmResidencyComponent @AssistedInject constructor(
@Composable
override fun BottomSheet() {
val state by model.state.collectAsStateWithLifecycle()
val bottomSheet by bottomSheetSlot.subscribeAsState()
val bottomSheetConfig = remember {
TangemBottomSheetConfig(
isShow = true,
@ -43,6 +59,18 @@ internal class DefaultConfirmResidencyComponent @AssistedInject constructor(
ConfirmResidencyBottomSheetContent(model = state, modifier = modifier)
},
)
bottomSheet.child?.instance?.BottomSheet()
}
private fun bottomSheetChild(
config: ConfirmResidencyBottomSheetConfig,
componentContext: ComponentContext,
): ComposableBottomSheetComponent = when (config) {
ConfirmResidencyBottomSheetConfig.SelectCountry -> selectCountryComponentFactory.create(
context = childByContext(componentContext),
params = SelectCountryComponent.Params(onDismiss = model.bottomSheetNavigation::dismiss),
)
}
@AssistedFactory

View file

@ -1,5 +1,7 @@
package com.tangem.features.onramp.model
import com.arkivanov.decompose.router.slot.SlotNavigation
import com.arkivanov.decompose.router.slot.activate
import com.tangem.core.decompose.di.ComponentScoped
import com.tangem.core.decompose.model.Model
import com.tangem.core.decompose.model.ParamsContainer
@ -7,6 +9,7 @@ import com.tangem.core.ui.extensions.resourceReference
import com.tangem.features.onramp.component.ConfirmResidencyComponent
import com.tangem.features.onramp.entity.ConfirmResidencyUM
import com.tangem.features.onramp.impl.R
import com.tangem.features.onramp.selectcountry.entity.ConfirmResidencyBottomSheetConfig
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.flow.MutableStateFlow
import javax.inject.Inject
@ -18,6 +21,7 @@ internal class ConfirmResidencyModel @Inject constructor(
) : Model() {
private val params: ConfirmResidencyComponent.Params = paramsContainer.require()
val bottomSheetNavigation: SlotNavigation<ConfirmResidencyBottomSheetConfig> = SlotNavigation()
val state: MutableStateFlow<ConfirmResidencyUM> = MutableStateFlow(
ConfirmResidencyUM(
country = params.countryName,
@ -48,6 +52,6 @@ internal class ConfirmResidencyModel @Inject constructor(
}
private fun onChangeClick() {
// TODO: [REDACTED_JIRA]
bottomSheetNavigation.activate(ConfirmResidencyBottomSheetConfig.SelectCountry)
}
}

View file

@ -0,0 +1,54 @@
package com.tangem.features.onramp.selectcountry
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.tangem.core.decompose.context.AppComponentContext
import com.tangem.core.decompose.model.getOrCreateModel
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent
import com.tangem.features.onramp.selectcountry.model.OnrampSelectCountryModel
import com.tangem.features.onramp.selectcountry.ui.OnrampCountryList
import com.tangem.features.onramp.selectcountry.ui.SelectCountryBottomSheet
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
internal class DefaultSelectCountryComponent @AssistedInject constructor(
@Assisted appComponentContext: AppComponentContext,
@Assisted private val params: SelectCountryComponent.Params,
) : SelectCountryComponent, AppComponentContext by appComponentContext {
private val model: OnrampSelectCountryModel = getOrCreateModel(params)
override fun dismiss() {
params.onDismiss()
}
@Composable
override fun BottomSheet() {
val state by model.state.collectAsStateWithLifecycle()
val bottomSheetConfig = remember {
TangemBottomSheetConfig(
isShow = true,
onDismissRequest = ::dismiss,
content = TangemBottomSheetConfigContent.Empty,
)
}
SelectCountryBottomSheet(
config = bottomSheetConfig,
content = { OnrampCountryList(state = state, modifier = Modifier.fillMaxSize()) },
)
}
@AssistedFactory
interface Factory : SelectCountryComponent.Factory {
override fun create(
context: AppComponentContext,
params: SelectCountryComponent.Params,
): DefaultSelectCountryComponent
}
}

View file

@ -0,0 +1,11 @@
package com.tangem.features.onramp.selectcountry
import com.tangem.core.decompose.factory.ComponentFactory
import com.tangem.core.ui.decompose.ComposableBottomSheetComponent
interface SelectCountryComponent : ComposableBottomSheetComponent {
data class Params(val onDismiss: () -> Unit)
interface Factory : ComponentFactory<Params, SelectCountryComponent>
}

View file

@ -0,0 +1,20 @@
package com.tangem.features.onramp.selectcountry.di
import com.tangem.features.onramp.selectcountry.DefaultSelectCountryComponent
import com.tangem.features.onramp.selectcountry.SelectCountryComponent
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 OnrampSelectCountryComponentModule {
@Binds
@Singleton
fun bindOnrampSelectCountryComponentFactory(
factory: DefaultSelectCountryComponent.Factory,
): SelectCountryComponent.Factory
}

View file

@ -0,0 +1,20 @@
package com.tangem.features.onramp.selectcountry.di
import com.tangem.core.decompose.di.DecomposeComponent
import com.tangem.core.decompose.model.Model
import com.tangem.features.onramp.selectcountry.model.OnrampSelectCountryModel
import dagger.Binds
import dagger.Module
import dagger.hilt.InstallIn
import dagger.multibindings.ClassKey
import dagger.multibindings.IntoMap
@Module
@InstallIn(DecomposeComponent::class)
internal interface OnrampSelectCountryModelModule {
@Binds
@IntoMap
@ClassKey(OnrampSelectCountryModel::class)
fun bindOnrampSelectCountryModel(model: OnrampSelectCountryModel): Model
}

View file

@ -0,0 +1,10 @@
package com.tangem.features.onramp.selectcountry.entity
import kotlinx.serialization.Serializable
@Serializable
internal sealed class ConfirmResidencyBottomSheetConfig {
@Serializable
data object SelectCountry : ConfirmResidencyBottomSheetConfig()
}

View file

@ -0,0 +1,19 @@
package com.tangem.features.onramp.selectcountry.entity
import androidx.compose.runtime.Immutable
import com.tangem.core.ui.components.fields.entity.SearchBarUM
@Immutable
internal sealed interface CountriesListItemUM {
/** Unique ID */
val id: Any
data class SearchBar(
override val id: Any = "search_bar",
val searchBarUM: SearchBarUM,
) : CountriesListItemUM
data class Country(val state: CountryItemState) : CountriesListItemUM {
override val id = state.id
}
}

View file

@ -0,0 +1,25 @@
package com.tangem.features.onramp.selectcountry.entity
import androidx.compose.runtime.Immutable
@Immutable
internal sealed interface CountryItemState {
/** Unique id */
val id: String
data class Loading(override val id: String) : CountryItemState
data class Unavailable(
override val id: String,
val flagUrl: String,
val countryName: String,
) : CountryItemState
data class Content(
override val id: String,
val flagUrl: String,
val countryName: String,
val isSelected: Boolean,
val onClick: () -> Unit,
) : CountryItemState
}

View file

@ -0,0 +1,23 @@
package com.tangem.features.onramp.selectcountry.entity
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
internal data class CountryListUM(val items: ImmutableList<CountriesListItemUM>) {
/** Get search bar if it exists */
fun getSearchBar(): CountriesListItemUM.SearchBar? {
return items.firstOrNull() as? CountriesListItemUM.SearchBar
}
/** Get tokens */
fun getTokens(): ImmutableList<CountriesListItemUM> {
if (getSearchBar() == null) return items
return if (items.size > 1) {
items.subList(fromIndex = 1, toIndex = items.size)
} else {
persistentListOf()
}
}
}

View file

@ -0,0 +1,54 @@
package com.tangem.features.onramp.selectcountry.entity
import com.tangem.features.onramp.selectcountry.entity.transformer.UpdateCountryItemsTransformer
import com.tangem.features.onramp.selectcountry.model.MockedCountriesData
import com.tangem.features.onramp.utils.SearchBarUMTransformer
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update
import timber.log.Timber
import javax.inject.Inject
internal class CountryListUMController @Inject constructor() {
val state: StateFlow<CountryListUM> get() = _state.asStateFlow()
private val _state: MutableStateFlow<CountryListUM> = MutableStateFlow(
value = CountryListUM(
items = MockedCountriesData.getLoadingItems().map(CountriesListItemUM::Country).toImmutableList(),
),
)
fun update(transformer: UpdateCountryItemsTransformer) {
Timber.d("Applying ${transformer::class.simpleName}")
_state.update(transformer::transform)
}
fun update(transformer: SearchBarUMTransformer) {
Timber.d("Applying ${transformer::class.simpleName}")
_state.update { prevState ->
val searchBarItem = prevState.getSearchBar()
if (searchBarItem != null) {
val updatedSearchBar =
searchBarItem.copy(searchBarUM = transformer.transform(searchBarItem.searchBarUM))
prevState.copy(
items = persistentListOf(
updatedSearchBar,
*prevState.getTokens().toTypedArray(),
),
)
} else {
prevState
}
}
}
/** Get search bar if it exists */
fun getSearchBar(): CountriesListItemUM.SearchBar? {
return _state.value.getSearchBar()
}
}

View file

@ -0,0 +1,40 @@
package com.tangem.features.onramp.selectcountry.entity.transformer
import com.tangem.core.ui.components.fields.entity.SearchBarUM
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.features.onramp.impl.R
import com.tangem.features.onramp.selectcountry.entity.CountriesListItemUM
import com.tangem.features.onramp.selectcountry.entity.CountryItemState
import com.tangem.features.onramp.selectcountry.entity.CountryListUM
import kotlinx.collections.immutable.toImmutableList
internal class UpdateCountryItemsTransformer(
private val countries: List<CountryItemState>,
private val onQueryChange: (String) -> Unit,
private val onActiveChange: (Boolean) -> Unit,
) {
fun transform(prevState: CountryListUM): CountryListUM {
val searchBarItem = prevState.getSearchBar() ?: createSearchBarItem()
return prevState.copy(
items = (listOfNotNull(searchBarItem) + countries.map(::convertCountry)).toImmutableList(),
)
}
// TODO: Temporarily. Will be refactored after implement domain
private fun convertCountry(state: CountryItemState): CountriesListItemUM.Country =
CountriesListItemUM.Country(state)
private fun createSearchBarItem(): CountriesListItemUM.SearchBar {
return CountriesListItemUM.SearchBar(
searchBarUM = SearchBarUM(
placeholderText = resourceReference(id = R.string.common_search),
query = "",
onQueryChange = onQueryChange,
isActive = false,
onActiveChange = onActiveChange,
),
)
}
}

View file

@ -0,0 +1,37 @@
package com.tangem.features.onramp.selectcountry.model
import com.tangem.features.onramp.selectcountry.entity.CountryItemState
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
// TODO: Temporarily. Will be deleted after implement domain
@Suppress("MagicNumber")
internal object MockedCountriesData {
fun getCountryItems(): Flow<List<CountryItemState>> = flow {
delay(3_000) // simulate network call
emit(getMockedCountries())
}
fun getLoadingItems(): List<CountryItemState> = MutableList(5) {
CountryItemState.Loading("Loading #$it")
}
private fun getMockedCountries(): List<CountryItemState> = MutableList(50) { index ->
if (index % 5 == 0) {
CountryItemState.Unavailable(
id = "Country #$index",
flagUrl = "",
countryName = "Country $index",
)
} else {
CountryItemState.Content(
id = "Country #$index",
flagUrl = "",
countryName = "Country $index",
onClick = {},
isSelected = index == 7,
)
}
}
}

View file

@ -0,0 +1,77 @@
package com.tangem.features.onramp.selectcountry.model
import com.tangem.core.decompose.di.ComponentScoped
import com.tangem.core.decompose.model.Model
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.features.onramp.impl.R
import com.tangem.features.onramp.selectcountry.entity.CountryItemState
import com.tangem.features.onramp.selectcountry.entity.CountryListUM
import com.tangem.features.onramp.selectcountry.entity.CountryListUMController
import com.tangem.features.onramp.selectcountry.entity.transformer.UpdateCountryItemsTransformer
import com.tangem.features.onramp.utils.SearchManager
import com.tangem.features.onramp.utils.UpdateSearchBarActiveStateTransformer
import com.tangem.features.onramp.utils.UpdateSearchQueryTransformer
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch
import javax.inject.Inject
@ComponentScoped
internal class OnrampSelectCountryModel @Inject constructor(
override val dispatchers: CoroutineDispatcherProvider,
private val searchManager: SearchManager,
private val countryListUMController: CountryListUMController,
) : Model() {
val state: StateFlow<CountryListUM> = countryListUMController.state
init {
subscribeOnUpdateState()
}
private fun subscribeOnUpdateState() {
combine(
flow = MockedCountriesData.getCountryItems(),
flow2 = searchManager.query,
) { countryItems, query ->
val filteredCountryItems = countryItems.filterByQuery(query = query)
UpdateCountryItemsTransformer(
countries = filteredCountryItems,
onQueryChange = ::onSearchQueryChange,
onActiveChange = ::onSearchBarActiveChange,
)
}
.onEach(countryListUMController::update)
.flowOn(dispatchers.main)
.launchIn(modelScope)
}
private fun onSearchQueryChange(newQuery: String) {
val searchBar = countryListUMController.getSearchBar()
if (searchBar?.searchBarUM?.query == newQuery) return
modelScope.launch {
countryListUMController.update(transformer = UpdateSearchQueryTransformer(newQuery))
searchManager.update(newQuery)
}
}
private fun onSearchBarActiveChange(isActive: Boolean) {
countryListUMController.update(
transformer = UpdateSearchBarActiveStateTransformer(
isActive = isActive,
placeHolder = resourceReference(id = R.string.common_search),
),
)
}
// TODO: Temporarily. Will be refactored after implement domain
private fun List<CountryItemState>.filterByQuery(query: String): List<CountryItemState> {
return filter {
(it as? CountryItemState.Content)?.countryName?.contains(query) == true ||
(it as? CountryItemState.Unavailable)?.countryName?.contains(query) == true
}
}
}

View file

@ -0,0 +1,134 @@
package com.tangem.features.onramp.selectcountry.ui
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.composed
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.style.TextOverflow
import coil.compose.AsyncImage
import com.tangem.core.ui.components.CircleShimmer
import com.tangem.core.ui.components.RectangleShimmer
import com.tangem.core.ui.components.fields.SearchBar
import com.tangem.core.ui.res.TangemTheme
import com.tangem.features.onramp.impl.R
import com.tangem.features.onramp.selectcountry.entity.CountriesListItemUM
import com.tangem.features.onramp.selectcountry.entity.CountryItemState
@Composable
internal fun CountryListItem(state: CountriesListItemUM, modifier: Modifier = Modifier) {
when (state) {
is CountriesListItemUM.Country -> CountryItem(
state = state.state,
modifier = modifier.countryClickable(state.state).padding(all = TangemTheme.dimens.spacing16),
)
is CountriesListItemUM.SearchBar -> SearchBar(
state = state.searchBarUM,
modifier = modifier.padding(all = TangemTheme.dimens.spacing16),
)
}
}
@Composable
private fun CountryItem(state: CountryItemState, modifier: Modifier = Modifier) {
when (state) {
is CountryItemState.Content -> ContentCountryItem(state = state, modifier = modifier)
is CountryItemState.Loading -> LoadingCountryItem(modifier)
is CountryItemState.Unavailable -> UnavailableCountryItem(state = state, modifier = modifier)
}
}
@Composable
private fun ContentCountryItem(state: CountryItemState.Content, modifier: Modifier = Modifier) {
Row(
modifier = modifier,
horizontalArrangement = Arrangement.spacedBy(space = TangemTheme.dimens.spacing12),
verticalAlignment = Alignment.CenterVertically,
) {
AsyncImage(
modifier = Modifier.size(TangemTheme.dimens.size36),
model = state.flagUrl,
contentDescription = null,
)
Text(
text = state.countryName,
modifier = Modifier.weight(1F),
overflow = TextOverflow.Ellipsis,
maxLines = 1,
style = TangemTheme.typography.subtitle2,
color = TangemTheme.colors.text.primary1,
)
if (state.isSelected) {
Icon(
painter = painterResource(id = R.drawable.ic_check_24),
contentDescription = null,
modifier = Modifier.size(TangemTheme.dimens.size24),
tint = TangemTheme.colors.icon.accent,
)
}
}
}
@Composable
private fun LoadingCountryItem(modifier: Modifier = Modifier) {
Row(modifier = modifier, horizontalArrangement = Arrangement.spacedBy(space = TangemTheme.dimens.spacing12)) {
CircleShimmer(modifier = Modifier.size(TangemTheme.dimens.size36))
RectangleShimmer(
modifier = Modifier
.align(Alignment.CenterVertically)
.size(width = TangemTheme.dimens.size70, height = TangemTheme.dimens.size12),
radius = TangemTheme.dimens.radius4,
)
}
}
@Composable
@Suppress("MagicNumber")
private fun UnavailableCountryItem(state: CountryItemState.Unavailable, modifier: Modifier = Modifier) {
Row(
modifier = modifier,
horizontalArrangement = Arrangement.spacedBy(space = TangemTheme.dimens.spacing12),
verticalAlignment = Alignment.CenterVertically,
) {
AsyncImage(
modifier = Modifier
.size(TangemTheme.dimens.size36)
.alpha(0.4F),
model = state.flagUrl,
contentDescription = null,
)
Text(
modifier = Modifier.weight(1F),
text = state.countryName,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
style = TangemTheme.typography.subtitle2,
color = TangemTheme.colors.text.disabled,
)
Text(
text = "Unavailable",
maxLines = 1,
style = TangemTheme.typography.body2,
color = TangemTheme.colors.text.tertiary,
)
}
}
@OptIn(ExperimentalFoundationApi::class)
private fun Modifier.countryClickable(state: CountryItemState): Modifier = composed {
when (state) {
is CountryItemState.Content -> combinedClickable(onClick = state.onClick)
is CountryItemState.Loading,
is CountryItemState.Unavailable,
-> this
}
}

View file

@ -0,0 +1,38 @@
package com.tangem.features.onramp.selectcountry.ui
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
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.features.onramp.selectcountry.entity.CountryListUM
@Composable
internal fun SelectCountryBottomSheet(config: TangemBottomSheetConfig, content: @Composable () -> Unit) {
TangemBottomSheet<TangemBottomSheetConfigContent.Empty>(
config = config,
content = { content() },
)
}
@Composable
internal fun OnrampCountryList(state: CountryListUM, modifier: Modifier = Modifier) {
LazyColumn(modifier = modifier) {
items(
items = state.items,
key = { item -> item.id },
contentType = { item -> item::class.java },
itemContent = { item ->
CountryListItem(
state = item,
modifier = Modifier
.animateItem()
.fillMaxWidth(),
)
},
)
}
}

View file

@ -1,6 +1,7 @@
package com.tangem.features.onramp.tokenlist.entity
import com.tangem.core.ui.components.tokenlist.state.TokensListItemUM
import com.tangem.features.onramp.utils.SearchBarUMTransformer
import kotlinx.collections.immutable.persistentListOf
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
@ -35,6 +36,28 @@ internal class TokenListUMController @Inject constructor() {
_state.update(transformer::transform)
}
fun update(transformer: SearchBarUMTransformer) {
Timber.d("Applying ${transformer::class.simpleName}")
_state.update { prevState ->
val searchBarItem = prevState.getSearchBar()
if (searchBarItem != null) {
val updatedSearchBar = searchBarItem.copy(
searchBarUM = transformer.transform(searchBarItem.searchBarUM),
)
prevState.copy(
availableItems = persistentListOf(
updatedSearchBar,
*prevState.getTokens().toTypedArray(),
),
)
} else {
prevState
}
}
}
/** Get search bar if it exists */
fun getSearchBar(): TokensListItemUM.SearchBar? {
return _state.value.getSearchBar()

View file

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

View file

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

View file

@ -3,6 +3,7 @@ package com.tangem.features.onramp.tokenlist.model
import arrow.core.getOrElse
import com.tangem.core.decompose.model.Model
import com.tangem.core.decompose.model.ParamsContainer
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase
@ -11,15 +12,16 @@ import com.tangem.domain.exchange.RampStateManager
import com.tangem.domain.tokens.GetTokenListUseCase
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.domain.tokens.model.TokenList
import com.tangem.features.onramp.impl.R
import com.tangem.domain.wallets.usecase.GetWalletsUseCase
import com.tangem.features.onramp.entity.OnrampOperation
import com.tangem.features.onramp.tokenlist.OnrampTokenListComponent
import com.tangem.features.onramp.tokenlist.entity.TokenListUM
import com.tangem.features.onramp.tokenlist.entity.TokenListUMController
import com.tangem.features.onramp.tokenlist.entity.transformer.UpdateSearchBarActiveStateTransformer
import com.tangem.features.onramp.tokenlist.entity.transformer.UpdateSearchQueryTransformer
import com.tangem.features.onramp.tokenlist.entity.transformer.UpdateTokenItemsTransformer
import com.tangem.features.onramp.tokenlist.utils.SearchTokensManager
import com.tangem.features.onramp.utils.SearchManager
import com.tangem.features.onramp.utils.UpdateSearchBarActiveStateTransformer
import com.tangem.features.onramp.utils.UpdateSearchQueryTransformer
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch
@ -30,7 +32,7 @@ internal class OnrampTokenListModel @Inject constructor(
paramsContainer: ParamsContainer,
override val dispatchers: CoroutineDispatcherProvider,
private val tokenListUMController: TokenListUMController,
private val searchTokensManager: SearchTokensManager,
private val searchManager: SearchManager,
private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
private val getBalanceHidingSettingsUseCase: GetBalanceHidingSettingsUseCase,
private val getTokenListUseCase: GetTokenListUseCase,
@ -54,7 +56,7 @@ internal class OnrampTokenListModel @Inject constructor(
flow = getTokenListUseCase.launch(userWalletId = params.userWalletId).distinctUntilChanged(),
flow2 = getSelectedAppCurrencyUseCase().map { it.getOrElse { AppCurrency.Default } }.distinctUntilChanged(),
flow3 = getBalanceHidingSettingsUseCase().map { it.isBalanceHidden }.distinctUntilChanged(),
flow4 = searchTokensManager.query,
flow4 = searchManager.query,
) { maybeTokenList, appCurrency, isBalanceHidden, query ->
val currencies = maybeTokenList.getOrElse(
ifLoading = { it ?: TokenList.Empty },
@ -88,12 +90,17 @@ internal class OnrampTokenListModel @Inject constructor(
modelScope.launch {
tokenListUMController.update(transformer = UpdateSearchQueryTransformer(newQuery))
searchTokensManager.update(newQuery)
searchManager.update(newQuery)
}
}
private fun onSearchBarActiveChange(isActive: Boolean) {
tokenListUMController.update(transformer = UpdateSearchBarActiveStateTransformer(isActive))
tokenListUMController.update(
transformer = UpdateSearchBarActiveStateTransformer(
isActive = isActive,
placeHolder = resourceReference(id = R.string.common_search),
),
)
}
private fun List<CryptoCurrencyStatus>.filterByQuery(query: String): List<CryptoCurrencyStatus> {

View file

@ -0,0 +1,14 @@
package com.tangem.features.onramp.utils
import com.tangem.core.ui.components.fields.entity.SearchBarUM
import com.tangem.utils.transformer.Transformer
/**
* Base [SearchBarUM] transformer
*
[REDACTED_AUTHOR]
*/
internal abstract class SearchBarUMTransformer : Transformer<SearchBarUM> {
abstract override fun transform(prevState: SearchBarUM): SearchBarUM
}

View file

@ -1,4 +1,4 @@
package com.tangem.features.onramp.tokenlist.utils
package com.tangem.features.onramp.utils
import com.tangem.utils.coroutines.JobHolder
import com.tangem.utils.coroutines.withDebounce
@ -12,7 +12,7 @@ import javax.inject.Inject
*
[REDACTED_AUTHOR]
*/
internal class SearchTokensManager @Inject constructor() {
internal class SearchManager @Inject constructor() {
val query: Flow<String>
get() = _query

View file

@ -0,0 +1,14 @@
package com.tangem.features.onramp.utils
import com.tangem.core.ui.components.fields.entity.SearchBarUM
import com.tangem.core.ui.extensions.TextReference
internal class UpdateSearchBarActiveStateTransformer(
private val isActive: Boolean,
private val placeHolder: TextReference,
) : SearchBarUMTransformer() {
override fun transform(prevState: SearchBarUM): SearchBarUM {
return prevState.copy(placeholderText = if (isActive) TextReference.EMPTY else placeHolder, isActive = isActive)
}
}

View file

@ -1,4 +1,4 @@
package com.tangem.features.onramp.tokenlist.entity.transformer
package com.tangem.features.onramp.utils
import com.tangem.core.ui.components.fields.entity.SearchBarUM