Updated on 2026-08-14
This commit is contained in:
parent
bed70c4dc8
commit
8966448261
30 changed files with 667 additions and 211 deletions
|
|
@ -74,6 +74,7 @@ dependencies {
|
|||
implementation(projects.domain.walletConnect)
|
||||
implementation(projects.domain.markets)
|
||||
implementation(projects.domain.manageTokens)
|
||||
implementation(projects.domain.onramp)
|
||||
|
||||
implementation(projects.common)
|
||||
implementation(projects.common.routing)
|
||||
|
|
@ -112,6 +113,7 @@ dependencies {
|
|||
implementation(projects.data.walletConnect)
|
||||
implementation(projects.data.markets)
|
||||
implementation(projects.data.manageTokens)
|
||||
implementation(projects.data.onramp)
|
||||
|
||||
/** Features */
|
||||
implementation(projects.features.onboarding)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,27 @@
|
|||
package com.tangem.tap.di.domain
|
||||
|
||||
import com.tangem.domain.onramp.GetOnrampCurrenciesUseCase
|
||||
import com.tangem.domain.onramp.OnrampSaveDefaultCurrencyUseCase
|
||||
import com.tangem.domain.onramp.repositories.OnrampRepository
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
internal object OnrampDomainModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideGetOnrampCurrenciesUseCase(onrampRepository: OnrampRepository): GetOnrampCurrenciesUseCase {
|
||||
return GetOnrampCurrenciesUseCase(onrampRepository)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideOnrampSaveDefaultCurrencyUseCase(onrampRepository: OnrampRepository): OnrampSaveDefaultCurrencyUseCase {
|
||||
return OnrampSaveDefaultCurrencyUseCase(onrampRepository)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,178 +0,0 @@
|
|||
package com.tangem.datasource.api.onramp
|
||||
|
||||
import com.tangem.datasource.api.common.response.ApiResponse
|
||||
import com.tangem.datasource.api.onramp.models.common.OnrampDestinationDTO
|
||||
import com.tangem.datasource.api.onramp.models.request.OnrampPairsRequest
|
||||
import com.tangem.datasource.api.onramp.models.response.OnrampDataResponse
|
||||
import com.tangem.datasource.api.onramp.models.response.OnrampQuoteResponse
|
||||
import com.tangem.datasource.api.onramp.models.response.OnrampStatusResponse
|
||||
import com.tangem.datasource.api.onramp.models.response.model.OnrampCountryDTO
|
||||
import com.tangem.datasource.api.onramp.models.response.model.OnrampCurrencyDTO
|
||||
import com.tangem.datasource.api.onramp.models.response.model.OnrampPairDTO
|
||||
import com.tangem.datasource.api.onramp.models.response.model.PaymentMethodDTO
|
||||
|
||||
internal class MockedOnrampApi : OnrampApi {
|
||||
override suspend fun getCurrencies(): ApiResponse<List<OnrampCurrencyDTO>> = ApiResponse.Success(
|
||||
COUNTRIES.map(OnrampCountryDTO::defaultCurrency),
|
||||
)
|
||||
|
||||
override suspend fun getCountries(): ApiResponse<List<OnrampCountryDTO>> = ApiResponse.Success(COUNTRIES + RUSSIA)
|
||||
|
||||
override suspend fun getCountryByIp(): ApiResponse<OnrampCountryDTO> = ApiResponse.Success(RUSSIA)
|
||||
|
||||
override suspend fun getPaymentMethods(): ApiResponse<List<PaymentMethodDTO>> = ApiResponse.Success(
|
||||
listOf(
|
||||
PaymentMethodDTO(id = "google", name = "Google Play", image = ""),
|
||||
PaymentMethodDTO(id = "apple", name = "Apple Pay", image = ""),
|
||||
PaymentMethodDTO(id = "card", name = "Card", image = ""),
|
||||
),
|
||||
)
|
||||
|
||||
override suspend fun getPairs(body: OnrampPairsRequest): ApiResponse<List<OnrampPairDTO>> = ApiResponse.Success(
|
||||
listOf(
|
||||
OnrampPairDTO(
|
||||
fromCurrencyCode = "USD",
|
||||
to = OnrampDestinationDTO(contractAddress = "0xcontract_address", network = "ethereum"),
|
||||
providers = listOf(),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
override suspend fun getQuote(
|
||||
fromCurrencyCode: String,
|
||||
toContractAddress: String,
|
||||
toNetwork: String,
|
||||
paymentMethod: String,
|
||||
countryCode: String,
|
||||
fromAmount: String,
|
||||
toDecimals: Int,
|
||||
providerId: String,
|
||||
): ApiResponse<OnrampQuoteResponse> {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override suspend fun getData(
|
||||
fromCurrencyCode: String,
|
||||
toContractAddress: String,
|
||||
toNetwork: String,
|
||||
paymentMethod: String,
|
||||
countryCode: String,
|
||||
fromAmount: String,
|
||||
toDecimals: Int,
|
||||
providerId: String,
|
||||
toAddress: String,
|
||||
redirectUrl: String,
|
||||
language: String?,
|
||||
theme: String?,
|
||||
requestId: String,
|
||||
): ApiResponse<OnrampDataResponse> {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override suspend fun getStatus(txId: String): ApiResponse<OnrampStatusResponse> {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
private companion object {
|
||||
private val RUSSIA = OnrampCountryDTO(
|
||||
name = "Russia",
|
||||
code = "RU",
|
||||
image = "https://hatscripts.github.io/circle-flags/flags/ru.svg",
|
||||
alpha3 = "RUS",
|
||||
continent = "",
|
||||
defaultCurrency = OnrampCurrencyDTO(
|
||||
name = "Russian ruble",
|
||||
code = "RUB",
|
||||
image = "https://hatscripts.github.io/circle-flags/flags/ru.svg",
|
||||
precision = 2,
|
||||
),
|
||||
onrampAvailable = false,
|
||||
)
|
||||
private val COUNTRIES = listOf(
|
||||
OnrampCountryDTO(
|
||||
name = "United States of America",
|
||||
code = "USA",
|
||||
image = "https://hatscripts.github.io/circle-flags/flags/us.svg",
|
||||
alpha3 = "USA",
|
||||
continent = "",
|
||||
defaultCurrency = OnrampCurrencyDTO(
|
||||
name = "US Dollar",
|
||||
code = "USD",
|
||||
image = "https://hatscripts.github.io/circle-flags/flags/us.svg",
|
||||
precision = 2,
|
||||
),
|
||||
onrampAvailable = true,
|
||||
),
|
||||
OnrampCountryDTO(
|
||||
name = "Europe Union",
|
||||
code = "EU",
|
||||
image = "https://hatscripts.github.io/circle-flags/flags/eu.svg",
|
||||
alpha3 = "EUR",
|
||||
continent = "",
|
||||
defaultCurrency = OnrampCurrencyDTO(
|
||||
name = "Euro",
|
||||
code = "EUR",
|
||||
image = "https://hatscripts.github.io/circle-flags/flags/eu.svg",
|
||||
precision = 2,
|
||||
),
|
||||
onrampAvailable = true,
|
||||
),
|
||||
OnrampCountryDTO(
|
||||
name = "Great Britain",
|
||||
code = "GB",
|
||||
image = "https://hatscripts.github.io/circle-flags/flags/gb.svg",
|
||||
alpha3 = "GB",
|
||||
continent = "",
|
||||
defaultCurrency = OnrampCurrencyDTO(
|
||||
name = "British Pound Sterling",
|
||||
code = "GBP",
|
||||
image = "https://hatscripts.github.io/circle-flags/flags/gb.svg",
|
||||
precision = 2,
|
||||
),
|
||||
onrampAvailable = true,
|
||||
),
|
||||
OnrampCountryDTO(
|
||||
name = "CANADA",
|
||||
code = "CA",
|
||||
image = "https://hatscripts.github.io/circle-flags/flags/ca.svg",
|
||||
alpha3 = "CA",
|
||||
continent = "",
|
||||
defaultCurrency = OnrampCurrencyDTO(
|
||||
name = "Canadian Dollar",
|
||||
code = "CAD",
|
||||
image = "https://hatscripts.github.io/circle-flags/flags/ca.svg",
|
||||
precision = 2,
|
||||
),
|
||||
onrampAvailable = true,
|
||||
),
|
||||
OnrampCountryDTO(
|
||||
name = "Hon Kong",
|
||||
code = "HK",
|
||||
image = "https://hatscripts.github.io/circle-flags/flags/hk.svg",
|
||||
alpha3 = "HK",
|
||||
continent = "",
|
||||
defaultCurrency = OnrampCurrencyDTO(
|
||||
name = "Hon Kong Dollar",
|
||||
code = "HKD",
|
||||
image = "https://hatscripts.github.io/circle-flags/flags/hk.svg",
|
||||
precision = 2,
|
||||
),
|
||||
onrampAvailable = true,
|
||||
),
|
||||
OnrampCountryDTO(
|
||||
name = "Australia",
|
||||
code = "AU",
|
||||
image = "https://hatscripts.github.io/circle-flags/flags/au.svg",
|
||||
alpha3 = "AU",
|
||||
continent = "",
|
||||
defaultCurrency = OnrampCurrencyDTO(
|
||||
name = "Australian Dollar",
|
||||
code = "AUD",
|
||||
image = "https://hatscripts.github.io/circle-flags/flags/au.svg",
|
||||
precision = 2,
|
||||
),
|
||||
onrampAvailable = true,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -11,7 +11,6 @@ import com.tangem.datasource.api.common.config.managers.ProdApiConfigsManager
|
|||
import com.tangem.datasource.api.common.response.ApiResponseCallAdapterFactory
|
||||
import com.tangem.datasource.api.express.TangemExpressApi
|
||||
import com.tangem.datasource.api.markets.TangemTechMarketsApi
|
||||
import com.tangem.datasource.api.onramp.MockedOnrampApi
|
||||
import com.tangem.datasource.api.onramp.OnrampApi
|
||||
import com.tangem.datasource.api.stakekit.StakeKitApi
|
||||
import com.tangem.datasource.api.tangemTech.TangemTechApi
|
||||
|
|
@ -99,24 +98,22 @@ internal object NetworkModule {
|
|||
@Provides
|
||||
@Singleton
|
||||
fun provideOnrampApi(
|
||||
// @NetworkMoshi moshi: Moshi,
|
||||
// @ApplicationContext context: Context,
|
||||
// apiConfigsManager: ApiConfigsManager,
|
||||
// appLogsStore: AppLogsStore,
|
||||
@NetworkMoshi moshi: Moshi,
|
||||
@ApplicationContext context: Context,
|
||||
apiConfigsManager: ApiConfigsManager,
|
||||
appLogsStore: AppLogsStore,
|
||||
): OnrampApi {
|
||||
// TODO: Remove when backend will be ready - [REDACTED_TASK_KEY]
|
||||
return MockedOnrampApi()
|
||||
// return createApi(
|
||||
// id = ApiConfig.ID.Express,
|
||||
// moshi = moshi,
|
||||
// context = context,
|
||||
// apiConfigsManager = apiConfigsManager,
|
||||
// clientBuilder = {
|
||||
// addInterceptor(
|
||||
// NetworkLogsSaveInterceptor(appLogsStore),
|
||||
// )
|
||||
// },
|
||||
// )
|
||||
return createApi(
|
||||
id = ApiConfig.ID.Express,
|
||||
moshi = moshi,
|
||||
context = context,
|
||||
apiConfigsManager = apiConfigsManager,
|
||||
clientBuilder = {
|
||||
addInterceptor(
|
||||
NetworkLogsSaveInterceptor(appLogsStore),
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
|
|
|
|||
|
|
@ -103,6 +103,8 @@ object PreferencesKeys {
|
|||
|
||||
val SHOULD_SHOW_RING_PROMO_KEY by lazy { booleanPreferencesKey(name = "shouldShowRingPromo") }
|
||||
|
||||
val ONRAMP_DEFAULT_CURRENCY by lazy { stringPreferencesKey(name = "onrampDefaultCurrency") }
|
||||
|
||||
// region Permission
|
||||
fun getShouldShowPermission(permission: String) = booleanPreferencesKey("shouldShowPushPermission_$permission")
|
||||
|
||||
|
|
|
|||
|
|
@ -4,15 +4,24 @@ import com.tangem.data.onramp.converters.CountryConverter
|
|||
import com.tangem.data.onramp.converters.CurrencyConverter
|
||||
import com.tangem.datasource.api.common.response.getOrThrow
|
||||
import com.tangem.datasource.api.onramp.OnrampApi
|
||||
import com.tangem.datasource.api.onramp.models.response.model.OnrampCurrencyDTO
|
||||
import com.tangem.datasource.local.preferences.AppPreferencesStore
|
||||
import com.tangem.datasource.local.preferences.PreferencesKeys
|
||||
import com.tangem.datasource.local.preferences.utils.getObject
|
||||
import com.tangem.datasource.local.preferences.utils.getObjectSyncOrNull
|
||||
import com.tangem.datasource.local.preferences.utils.storeObject
|
||||
import com.tangem.domain.onramp.model.OnrampCountry
|
||||
import com.tangem.domain.onramp.model.OnrampCurrency
|
||||
import com.tangem.domain.onramp.repositories.OnrampRepository
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
internal class DefaultOnrampRepository(
|
||||
private val onrampApi: OnrampApi,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
private val appPreferencesStore: AppPreferencesStore,
|
||||
) : OnrampRepository {
|
||||
|
||||
private val currencyConverter = CurrencyConverter()
|
||||
|
|
@ -29,4 +38,23 @@ internal class DefaultOnrampRepository(
|
|||
.getOrThrow()
|
||||
.map(countryConverter::convert)
|
||||
}
|
||||
|
||||
override suspend fun saveDefaultCurrency(currency: OnrampCurrency) {
|
||||
appPreferencesStore.storeObject<OnrampCurrencyDTO>(
|
||||
key = PreferencesKeys.ONRAMP_DEFAULT_CURRENCY,
|
||||
value = currencyConverter.convertBack(currency),
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun getDefaultCurrencySync(): OnrampCurrency? {
|
||||
return appPreferencesStore
|
||||
.getObjectSyncOrNull<OnrampCurrencyDTO>(PreferencesKeys.ONRAMP_DEFAULT_CURRENCY)
|
||||
?.let(currencyConverter::convert)
|
||||
}
|
||||
|
||||
override fun getDefaultCurrency(): Flow<OnrampCurrency?> {
|
||||
return appPreferencesStore
|
||||
.getObject<OnrampCurrencyDTO>(PreferencesKeys.ONRAMP_DEFAULT_CURRENCY)
|
||||
.map { it?.let(currencyConverter::convert) }
|
||||
}
|
||||
}
|
||||
|
|
@ -2,9 +2,9 @@ package com.tangem.data.onramp.converters
|
|||
|
||||
import com.tangem.datasource.api.onramp.models.response.model.OnrampCurrencyDTO
|
||||
import com.tangem.domain.onramp.model.OnrampCurrency
|
||||
import com.tangem.utils.converter.Converter
|
||||
import com.tangem.utils.converter.TwoWayConverter
|
||||
|
||||
internal class CurrencyConverter : Converter<OnrampCurrencyDTO, OnrampCurrency> {
|
||||
internal class CurrencyConverter : TwoWayConverter<OnrampCurrencyDTO, OnrampCurrency> {
|
||||
|
||||
override fun convert(value: OnrampCurrencyDTO) = OnrampCurrency(
|
||||
name = value.name,
|
||||
|
|
@ -12,4 +12,11 @@ internal class CurrencyConverter : Converter<OnrampCurrencyDTO, OnrampCurrency>
|
|||
image = value.image,
|
||||
precision = value.precision,
|
||||
)
|
||||
|
||||
override fun convertBack(value: OnrampCurrency): OnrampCurrencyDTO = OnrampCurrencyDTO(
|
||||
name = value.name,
|
||||
code = value.code,
|
||||
image = value.image,
|
||||
precision = value.precision,
|
||||
)
|
||||
}
|
||||
|
|
@ -2,6 +2,7 @@ package com.tangem.data.onramp.di
|
|||
|
||||
import com.tangem.data.onramp.DefaultOnrampRepository
|
||||
import com.tangem.datasource.api.onramp.OnrampApi
|
||||
import com.tangem.datasource.local.preferences.AppPreferencesStore
|
||||
import com.tangem.domain.onramp.repositories.OnrampRepository
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import dagger.Module
|
||||
|
|
@ -16,10 +17,15 @@ internal object OnrampDataModule {
|
|||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideOnrampRepository(onrampApi: OnrampApi, dispatchers: CoroutineDispatcherProvider): OnrampRepository {
|
||||
fun provideOnrampRepository(
|
||||
onrampApi: OnrampApi,
|
||||
dispatchers: CoroutineDispatcherProvider,
|
||||
appPreferencesStore: AppPreferencesStore,
|
||||
): OnrampRepository {
|
||||
return DefaultOnrampRepository(
|
||||
onrampApi = onrampApi,
|
||||
dispatchers = dispatchers,
|
||||
appPreferencesStore = appPreferencesStore,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
package com.tangem.domain.onramp.model
|
||||
|
||||
data class OnrampCurrencies(val populars: List<OnrampCurrency>, val others: List<OnrampCurrency>)
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
package com.tangem.domain.onramp
|
||||
|
||||
import arrow.core.Either
|
||||
import com.tangem.domain.onramp.model.OnrampCurrencies
|
||||
import com.tangem.domain.onramp.repositories.OnrampRepository
|
||||
|
||||
class GetOnrampCurrenciesUseCase(
|
||||
private val onrampRepository: OnrampRepository,
|
||||
) {
|
||||
|
||||
suspend operator fun invoke(): Either<Throwable, OnrampCurrencies> {
|
||||
return Either.catch {
|
||||
val currenciesList = onrampRepository.getCurrencies()
|
||||
val (populars, others) = currenciesList.toSet()
|
||||
.partition { popularFiatCodes.contains(it.code.uppercase()) }
|
||||
OnrampCurrencies(populars = populars, others = others)
|
||||
}
|
||||
}
|
||||
|
||||
private companion object {
|
||||
val popularFiatCodes = setOf("USD", "EUR", "GBP", "CAD", "AUD", "HKD")
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
package com.tangem.domain.onramp
|
||||
|
||||
import com.tangem.domain.onramp.model.OnrampCurrency
|
||||
import com.tangem.domain.onramp.repositories.OnrampRepository
|
||||
|
||||
class OnrampSaveDefaultCurrencyUseCase(private val onrampRepository: OnrampRepository) {
|
||||
|
||||
suspend operator fun invoke(currency: OnrampCurrency) {
|
||||
onrampRepository.saveDefaultCurrency(currency)
|
||||
}
|
||||
}
|
||||
|
|
@ -2,8 +2,12 @@ package com.tangem.domain.onramp.repositories
|
|||
|
||||
import com.tangem.domain.onramp.model.OnrampCountry
|
||||
import com.tangem.domain.onramp.model.OnrampCurrency
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
interface OnrampRepository {
|
||||
suspend fun getCurrencies(): List<OnrampCurrency>
|
||||
suspend fun getCountries(): List<OnrampCountry>
|
||||
suspend fun saveDefaultCurrency(currency: OnrampCurrency)
|
||||
suspend fun getDefaultCurrencySync(): OnrampCurrency?
|
||||
fun getDefaultCurrency(): Flow<OnrampCurrency?>
|
||||
}
|
||||
|
|
@ -30,6 +30,7 @@ dependencies {
|
|||
implementation(projects.domain.balanceHiding.models)
|
||||
implementation(projects.domain.legacy)
|
||||
implementation(projects.domain.models)
|
||||
implementation(projects.domain.onramp)
|
||||
implementation(projects.domain.tokens)
|
||||
implementation(projects.domain.tokens.models)
|
||||
implementation(projects.domain.wallets)
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ internal class DefaultConfirmResidencyComponent @AssistedInject constructor(
|
|||
override fun BottomSheet() {
|
||||
val state by model.state.collectAsStateWithLifecycle()
|
||||
val bottomSheet by bottomSheetSlot.subscribeAsState()
|
||||
val bottomSheetConfig = remember {
|
||||
val bottomSheetConfig = remember(key1 = this) {
|
||||
TangemBottomSheetConfig(
|
||||
isShow = true,
|
||||
onDismissRequest = ::dismiss,
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package com.tangem.features.onramp.component.preview
|
|||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
|
||||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent
|
||||
|
|
@ -31,11 +32,13 @@ internal class PreviewConfirmResidencyComponent(
|
|||
@Composable
|
||||
override fun BottomSheet() {
|
||||
val state by previewState.collectAsStateWithLifecycle()
|
||||
val bottomSheetConfig = TangemBottomSheetConfig(
|
||||
isShow = true,
|
||||
onDismissRequest = ::dismiss,
|
||||
content = TangemBottomSheetConfigContent.Empty,
|
||||
)
|
||||
val bottomSheetConfig = remember(key1 = this) {
|
||||
TangemBottomSheetConfig(
|
||||
isShow = true,
|
||||
onDismissRequest = ::dismiss,
|
||||
content = TangemBottomSheetConfigContent.Empty,
|
||||
)
|
||||
}
|
||||
ConfirmResidencyBottomSheet(
|
||||
config = bottomSheetConfig,
|
||||
content = { modifier ->
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ internal class DefaultSelectCountryComponent @AssistedInject constructor(
|
|||
@Composable
|
||||
override fun BottomSheet() {
|
||||
val state by model.state.collectAsStateWithLifecycle()
|
||||
val bottomSheetConfig = remember {
|
||||
val bottomSheetConfig = remember(key1 = this) {
|
||||
TangemBottomSheetConfig(
|
||||
isShow = true,
|
||||
onDismissRequest = ::dismiss,
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ 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() {
|
||||
|
|
@ -22,12 +21,10 @@ internal class CountryListUMController @Inject constructor() {
|
|||
)
|
||||
|
||||
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()
|
||||
|
||||
|
|
|
|||
|
|
@ -6,15 +6,16 @@ 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 com.tangem.utils.transformer.Transformer
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
|
||||
internal class UpdateCountryItemsTransformer(
|
||||
private val countries: List<CountryItemState>,
|
||||
private val onQueryChange: (String) -> Unit,
|
||||
private val onActiveChange: (Boolean) -> Unit,
|
||||
) {
|
||||
) : Transformer<CountryListUM> {
|
||||
|
||||
fun transform(prevState: CountryListUM): CountryListUM {
|
||||
override fun transform(prevState: CountryListUM): CountryListUM {
|
||||
val searchBarItem = prevState.getSearchBar() ?: createSearchBarItem()
|
||||
|
||||
return prevState.copy(
|
||||
|
|
|
|||
|
|
@ -0,0 +1,56 @@
|
|||
package com.tangem.features.onramp.selectcurrency
|
||||
|
||||
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.selectcurrency.model.OnrampSelectCurrencyModel
|
||||
import com.tangem.features.onramp.selectcurrency.ui.OnrampCurrencyList
|
||||
import com.tangem.features.onramp.selectcurrency.ui.SelectCurrencyBottomSheet
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
|
||||
internal class DefaultSelectCurrencyComponent @AssistedInject constructor(
|
||||
@Assisted context: AppComponentContext,
|
||||
@Assisted params: SelectCurrencyComponent.Params,
|
||||
) : SelectCurrencyComponent, AppComponentContext by context {
|
||||
|
||||
private val model: OnrampSelectCurrencyModel = getOrCreateModel(params)
|
||||
|
||||
override fun dismiss() {
|
||||
model.dismiss()
|
||||
}
|
||||
|
||||
@Composable
|
||||
override fun BottomSheet() {
|
||||
val state by model.state.collectAsStateWithLifecycle()
|
||||
val bottomSheetConfig = remember(key1 = this) {
|
||||
TangemBottomSheetConfig(
|
||||
isShow = true,
|
||||
onDismissRequest = ::dismiss,
|
||||
content = TangemBottomSheetConfigContent.Empty,
|
||||
)
|
||||
}
|
||||
SelectCurrencyBottomSheet(
|
||||
config = bottomSheetConfig,
|
||||
content = {
|
||||
OnrampCurrencyList(modifier = Modifier.fillMaxSize(), state = state)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@AssistedFactory
|
||||
interface Factory : SelectCurrencyComponent.Factory {
|
||||
override fun create(
|
||||
context: AppComponentContext,
|
||||
params: SelectCurrencyComponent.Params,
|
||||
): DefaultSelectCurrencyComponent
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
package com.tangem.features.onramp.selectcurrency
|
||||
|
||||
import com.tangem.core.decompose.factory.ComponentFactory
|
||||
import com.tangem.core.ui.decompose.ComposableBottomSheetComponent
|
||||
|
||||
internal interface SelectCurrencyComponent : ComposableBottomSheetComponent {
|
||||
|
||||
data class Params(val onDismiss: () -> Unit)
|
||||
|
||||
interface Factory : ComponentFactory<Params, SelectCurrencyComponent>
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
package com.tangem.features.onramp.selectcurrency.di
|
||||
|
||||
import com.tangem.features.onramp.selectcurrency.DefaultSelectCurrencyComponent
|
||||
import com.tangem.features.onramp.selectcurrency.SelectCurrencyComponent
|
||||
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 OnrampSelectCurrencyComponentModule {
|
||||
|
||||
@Binds
|
||||
@Singleton
|
||||
fun bindSelectCurrencyComponentFactory(
|
||||
factory: DefaultSelectCurrencyComponent.Factory,
|
||||
): SelectCurrencyComponent.Factory
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
package com.tangem.features.onramp.selectcurrency.di
|
||||
|
||||
import com.tangem.core.decompose.di.DecomposeComponent
|
||||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.features.onramp.selectcurrency.model.OnrampSelectCurrencyModel
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.multibindings.ClassKey
|
||||
import dagger.multibindings.IntoMap
|
||||
|
||||
@Module
|
||||
@InstallIn(DecomposeComponent::class)
|
||||
internal interface OnrampSelectCurrencyModelModule {
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@ClassKey(OnrampSelectCurrencyModel::class)
|
||||
fun bindOnrampSelectCurrencyModel(model: OnrampSelectCurrencyModel): Model
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
package com.tangem.features.onramp.selectcurrency.entity
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import com.tangem.core.ui.components.fields.entity.SearchBarUM
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
||||
@Immutable
|
||||
internal sealed interface CurrenciesListUM {
|
||||
|
||||
val searchBarUM: SearchBarUM
|
||||
|
||||
data class Loading(
|
||||
override val searchBarUM: SearchBarUM,
|
||||
val sections: ImmutableList<CurrenciesSection<CurrencyItemState.Loading>>,
|
||||
) : CurrenciesListUM
|
||||
|
||||
data class Error(override val searchBarUM: SearchBarUM, val onRetry: () -> Unit) : CurrenciesListUM
|
||||
|
||||
data class Content(
|
||||
override val searchBarUM: SearchBarUM,
|
||||
val sections: ImmutableList<CurrenciesSection<CurrencyItemState.Content>>,
|
||||
) : CurrenciesListUM
|
||||
|
||||
fun copySealed(searchBarUM: SearchBarUM = this.searchBarUM): CurrenciesListUM {
|
||||
return when (this) {
|
||||
is Loading -> this.copy(searchBarUM = searchBarUM)
|
||||
is Error -> this.copy(searchBarUM = searchBarUM)
|
||||
is Content -> this.copy(searchBarUM = searchBarUM)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
package com.tangem.features.onramp.selectcurrency.entity
|
||||
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
||||
internal data class CurrenciesSection<T : CurrencyItemState>(val title: TextReference, val items: ImmutableList<T>)
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
package com.tangem.features.onramp.selectcurrency.entity
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import com.tangem.domain.onramp.model.OnrampCurrency
|
||||
|
||||
@Immutable
|
||||
internal sealed interface CurrencyItemState {
|
||||
|
||||
/** Unique ID */
|
||||
val id: String
|
||||
|
||||
data class Loading(override val id: String) : CurrencyItemState
|
||||
|
||||
data class Content(
|
||||
override val id: String,
|
||||
val onrampCurrency: OnrampCurrency,
|
||||
val onClick: () -> Unit,
|
||||
) : CurrencyItemState
|
||||
}
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
package com.tangem.features.onramp.selectcurrency.entity
|
||||
|
||||
import com.tangem.core.ui.components.fields.entity.SearchBarUM
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.features.onramp.selectcurrency.entity.transformer.UpdateCurrencyItemsTransformer
|
||||
import com.tangem.features.onramp.utils.SearchBarUMTransformer
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
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
|
||||
|
||||
internal class CurrencyListController(private val currencySearchBarUM: SearchBarUM) {
|
||||
val state: StateFlow<CurrenciesListUM> get() = _state.asStateFlow()
|
||||
private val _state: MutableStateFlow<CurrenciesListUM> = MutableStateFlow(value = getInitialState())
|
||||
|
||||
fun update(transformer: UpdateCurrencyItemsTransformer) {
|
||||
_state.update(transformer::transform)
|
||||
}
|
||||
|
||||
fun update(transformer: SearchBarUMTransformer) {
|
||||
_state.update { prevState ->
|
||||
val searchBarUM = transformer.transform(prevState.searchBarUM)
|
||||
prevState.copySealed(searchBarUM)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getInitialState(): CurrenciesListUM {
|
||||
return CurrenciesListUM.Loading(
|
||||
searchBarUM = currencySearchBarUM,
|
||||
sections = listOf(
|
||||
CurrenciesSection(stringReference("Popular fiats"), items = createLoadingItems("popular")),
|
||||
CurrenciesSection(stringReference("Other currencies"), items = createLoadingItems("other")),
|
||||
).toImmutableList(),
|
||||
)
|
||||
}
|
||||
|
||||
private fun createLoadingItems(prefix: String, size: Int = 5): ImmutableList<CurrencyItemState.Loading> =
|
||||
MutableList(size) { index -> CurrencyItemState.Loading("$prefix#$index") }.toImmutableList()
|
||||
}
|
||||
|
|
@ -0,0 +1,65 @@
|
|||
package com.tangem.features.onramp.selectcurrency.entity.transformer
|
||||
|
||||
import arrow.core.Either
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.domain.onramp.model.OnrampCurrencies
|
||||
import com.tangem.domain.onramp.model.OnrampCurrency
|
||||
import com.tangem.features.onramp.selectcurrency.entity.CurrenciesListUM
|
||||
import com.tangem.features.onramp.selectcurrency.entity.CurrenciesSection
|
||||
import com.tangem.features.onramp.selectcurrency.entity.CurrencyItemState
|
||||
import com.tangem.utils.transformer.Transformer
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
|
||||
internal class UpdateCurrencyItemsTransformer(
|
||||
private val maybeCurrencies: Either<Throwable, OnrampCurrencies>,
|
||||
private val query: String,
|
||||
private val onRetry: () -> Unit,
|
||||
private val onCurrencyClick: (OnrampCurrency) -> Unit,
|
||||
) : Transformer<CurrenciesListUM> {
|
||||
|
||||
override fun transform(prevState: CurrenciesListUM): CurrenciesListUM {
|
||||
return maybeCurrencies.fold(
|
||||
ifLeft = { CurrenciesListUM.Error(searchBarUM = prevState.searchBarUM, onRetry = onRetry) },
|
||||
ifRight = { currencies ->
|
||||
val populars = currencies.populars.filterByQuery()
|
||||
val others = currencies.others.filterByQuery()
|
||||
val sections = buildList {
|
||||
if (populars.isNotEmpty()) {
|
||||
add(
|
||||
CurrenciesSection(
|
||||
title = stringReference("Popular fiats"),
|
||||
items = populars.map(::convertCurrencyToUiModel).toImmutableList(),
|
||||
),
|
||||
)
|
||||
}
|
||||
if (others.isNotEmpty()) {
|
||||
add(
|
||||
CurrenciesSection(
|
||||
title = stringReference("Other currencies"),
|
||||
items = others.map(::convertCurrencyToUiModel).toImmutableList(),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
CurrenciesListUM.Content(
|
||||
searchBarUM = prevState.searchBarUM,
|
||||
sections = sections.toImmutableList(),
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
private fun List<OnrampCurrency>.filterByQuery(): List<OnrampCurrency> {
|
||||
return filter {
|
||||
it.code.lowercase().contains(query.lowercase()) || it.name.lowercase().contains(query.lowercase())
|
||||
}
|
||||
}
|
||||
|
||||
private fun convertCurrencyToUiModel(onrampCurrency: OnrampCurrency): CurrencyItemState.Content {
|
||||
return CurrencyItemState.Content(
|
||||
id = onrampCurrency.code,
|
||||
onrampCurrency = onrampCurrency,
|
||||
onClick = { onCurrencyClick(onrampCurrency) },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,107 @@
|
|||
package com.tangem.features.onramp.selectcurrency.model
|
||||
|
||||
import com.tangem.core.decompose.di.ComponentScoped
|
||||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.core.decompose.model.ParamsContainer
|
||||
import com.tangem.core.ui.components.fields.entity.SearchBarUM
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.domain.onramp.GetOnrampCurrenciesUseCase
|
||||
import com.tangem.domain.onramp.OnrampSaveDefaultCurrencyUseCase
|
||||
import com.tangem.domain.onramp.model.OnrampCurrency
|
||||
import com.tangem.features.onramp.impl.R
|
||||
import com.tangem.features.onramp.selectcurrency.SelectCurrencyComponent
|
||||
import com.tangem.features.onramp.selectcurrency.entity.CurrenciesListUM
|
||||
import com.tangem.features.onramp.selectcurrency.entity.CurrencyListController
|
||||
import com.tangem.features.onramp.selectcurrency.entity.transformer.UpdateCurrencyItemsTransformer
|
||||
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.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
|
||||
@ComponentScoped
|
||||
internal class OnrampSelectCurrencyModel @Inject constructor(
|
||||
override val dispatchers: CoroutineDispatcherProvider,
|
||||
private val searchManager: SearchManager,
|
||||
private val getOnrampCurrenciesUseCase: GetOnrampCurrenciesUseCase,
|
||||
private val saveDefaultCurrencyUseCase: OnrampSaveDefaultCurrencyUseCase,
|
||||
paramsContainer: ParamsContainer,
|
||||
) : Model() {
|
||||
|
||||
val state: StateFlow<CurrenciesListUM> get() = controller.state
|
||||
|
||||
private val params: SelectCurrencyComponent.Params = paramsContainer.require()
|
||||
private val controller = CurrencyListController(currencySearchBarUM = createSearchBarUM())
|
||||
private val refreshTrigger = MutableSharedFlow<Unit>()
|
||||
|
||||
init {
|
||||
subscribeOnUpdateState()
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
private fun subscribeOnUpdateState() {
|
||||
combine(
|
||||
flow = refreshTrigger.onStart { emit(Unit) }.flatMapLatest { flowOf(getOnrampCurrenciesUseCase.invoke()) },
|
||||
flow2 = searchManager.query,
|
||||
) { maybeCurrencies, query ->
|
||||
UpdateCurrencyItemsTransformer(
|
||||
maybeCurrencies = maybeCurrencies,
|
||||
query = query,
|
||||
onRetry = ::onRetry,
|
||||
onCurrencyClick = ::saveDefaultCurrency,
|
||||
)
|
||||
}
|
||||
.onEach(controller::update)
|
||||
.launchIn(modelScope)
|
||||
}
|
||||
|
||||
fun dismiss() {
|
||||
params.onDismiss()
|
||||
}
|
||||
|
||||
private fun saveDefaultCurrency(currency: OnrampCurrency) {
|
||||
modelScope.launch {
|
||||
saveDefaultCurrencyUseCase.invoke(currency)
|
||||
dismiss()
|
||||
}
|
||||
}
|
||||
|
||||
private fun onRetry() {
|
||||
modelScope.launch {
|
||||
refreshTrigger.emit(Unit)
|
||||
}
|
||||
}
|
||||
|
||||
private fun onSearchQueryChange(newQuery: String) {
|
||||
val searchBarUM = controller.state.value.searchBarUM
|
||||
if (searchBarUM.query == newQuery) return
|
||||
|
||||
modelScope.launch {
|
||||
controller.update(transformer = UpdateSearchQueryTransformer(newQuery))
|
||||
|
||||
searchManager.update(newQuery)
|
||||
}
|
||||
}
|
||||
|
||||
private fun onSearchBarActiveChange(isActive: Boolean) {
|
||||
controller.update(
|
||||
transformer = UpdateSearchBarActiveStateTransformer(
|
||||
isActive = isActive,
|
||||
placeHolder = resourceReference(id = R.string.common_search),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private fun createSearchBarUM(): SearchBarUM {
|
||||
return SearchBarUM(
|
||||
placeholderText = resourceReference(R.string.onramp_currency_search),
|
||||
query = "",
|
||||
onQueryChange = ::onSearchQueryChange,
|
||||
isActive = false,
|
||||
onActiveChange = ::onSearchBarActiveChange,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,145 @@
|
|||
package com.tangem.features.onramp.selectcurrency.ui
|
||||
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.LazyListScope
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.util.fastForEach
|
||||
import coil.compose.AsyncImage
|
||||
import com.tangem.core.ui.components.CircleShimmer
|
||||
import com.tangem.core.ui.components.TextShimmer
|
||||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheet
|
||||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
|
||||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent
|
||||
import com.tangem.core.ui.components.fields.SearchBar
|
||||
import com.tangem.core.ui.extensions.resolveReference
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.features.onramp.selectcurrency.entity.CurrenciesListUM
|
||||
import com.tangem.features.onramp.selectcurrency.entity.CurrencyItemState
|
||||
|
||||
@Composable
|
||||
internal fun SelectCurrencyBottomSheet(config: TangemBottomSheetConfig, content: @Composable () -> Unit) {
|
||||
TangemBottomSheet<TangemBottomSheetConfigContent.Empty>(
|
||||
config = config,
|
||||
content = { content() },
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
internal fun OnrampCurrencyList(state: CurrenciesListUM, modifier: Modifier = Modifier) {
|
||||
LazyColumn(
|
||||
modifier = modifier,
|
||||
contentPadding = PaddingValues(horizontal = TangemTheme.dimens.spacing16),
|
||||
) {
|
||||
item(key = "search_bar") {
|
||||
SearchBar(
|
||||
state = state.searchBarUM,
|
||||
modifier = Modifier.padding(bottom = TangemTheme.dimens.spacing20),
|
||||
)
|
||||
}
|
||||
when (state) {
|
||||
is CurrenciesListUM.Content -> currencyListContent(state = state)
|
||||
is CurrenciesListUM.Error -> TODO()
|
||||
is CurrenciesListUM.Loading -> currencyListLoading(state = state)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun LazyListScope.currencyListLoading(state: CurrenciesListUM.Loading) {
|
||||
state.sections.fastForEach { section ->
|
||||
item(key = section.title.toString()) { GroupTitle(title = section.title.resolveReference()) }
|
||||
items(
|
||||
items = section.items,
|
||||
key = { item -> item.id },
|
||||
contentType = { item -> item::class.java },
|
||||
itemContent = { CurrencyLoading(modifier = Modifier.padding(vertical = TangemTheme.dimens.spacing16)) },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun LazyListScope.currencyListContent(state: CurrenciesListUM.Content) {
|
||||
state.sections.fastForEach { section ->
|
||||
item(key = section.title.toString()) { GroupTitle(title = section.title.resolveReference()) }
|
||||
items(
|
||||
items = section.items,
|
||||
key = { item -> item.id },
|
||||
contentType = { item -> item::class.java },
|
||||
itemContent = { item ->
|
||||
CurrencyContent(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clickable(onClick = item.onClick)
|
||||
.padding(vertical = TangemTheme.dimens.spacing16),
|
||||
currency = item,
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun GroupTitle(title: String, modifier: Modifier = Modifier) {
|
||||
Text(
|
||||
modifier = modifier,
|
||||
text = title,
|
||||
style = TangemTheme.typography.subtitle2,
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun CurrencyContent(currency: CurrencyItemState.Content, modifier: Modifier = Modifier) {
|
||||
Row(
|
||||
modifier = modifier,
|
||||
horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing12),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
AsyncImage(
|
||||
modifier = Modifier
|
||||
.size(TangemTheme.dimens.size36)
|
||||
.clip(CircleShape),
|
||||
model = currency.onrampCurrency.image,
|
||||
contentDescription = null,
|
||||
)
|
||||
Column(verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing2)) {
|
||||
Text(
|
||||
text = currency.onrampCurrency.code,
|
||||
style = TangemTheme.typography.subtitle2,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
)
|
||||
Text(
|
||||
text = currency.onrampCurrency.name,
|
||||
style = TangemTheme.typography.caption2,
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun CurrencyLoading(modifier: Modifier = Modifier) {
|
||||
Row(
|
||||
modifier = modifier,
|
||||
horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing12),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
CircleShimmer(modifier = Modifier.size(TangemTheme.dimens.size36))
|
||||
Column(verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing6)) {
|
||||
TextShimmer(
|
||||
modifier = Modifier.width(width = TangemTheme.dimens.size70),
|
||||
style = TangemTheme.typography.subtitle2,
|
||||
)
|
||||
TextShimmer(
|
||||
modifier = Modifier.width(width = TangemTheme.dimens.size52),
|
||||
style = TangemTheme.typography.caption2,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -24,6 +24,7 @@ internal class SearchManager @Inject constructor() {
|
|||
suspend fun update(value: String) {
|
||||
coroutineScope {
|
||||
if (value.isEmpty()) {
|
||||
jobHolder.cancel()
|
||||
_query.value = value
|
||||
} else {
|
||||
withDebounce(jobHolder) { _query.value = value }
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue