diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 9029856167..f08ee1b88d 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -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) diff --git a/app/src/main/java/com/tangem/tap/di/domain/OnrampDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/OnrampDomainModule.kt new file mode 100644 index 0000000000..b485886c06 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/di/domain/OnrampDomainModule.kt @@ -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) + } +} \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/onramp/MockedOnrampApi.kt b/core/datasource/src/main/java/com/tangem/datasource/api/onramp/MockedOnrampApi.kt deleted file mode 100644 index a960766de8..0000000000 --- a/core/datasource/src/main/java/com/tangem/datasource/api/onramp/MockedOnrampApi.kt +++ /dev/null @@ -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> = ApiResponse.Success( - COUNTRIES.map(OnrampCountryDTO::defaultCurrency), - ) - - override suspend fun getCountries(): ApiResponse> = ApiResponse.Success(COUNTRIES + RUSSIA) - - override suspend fun getCountryByIp(): ApiResponse = ApiResponse.Success(RUSSIA) - - override suspend fun getPaymentMethods(): ApiResponse> = 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> = 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 { - 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 { - TODO("Not yet implemented") - } - - override suspend fun getStatus(txId: String): ApiResponse { - 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, - ), - ) - } -} \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/di/NetworkModule.kt b/core/datasource/src/main/java/com/tangem/datasource/di/NetworkModule.kt index 720e14ac0b..8b38ca983a 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/di/NetworkModule.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/di/NetworkModule.kt @@ -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 diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/preferences/PreferencesKeys.kt b/core/datasource/src/main/java/com/tangem/datasource/local/preferences/PreferencesKeys.kt index 10f76c5e66..60946f6ff4 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/local/preferences/PreferencesKeys.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/local/preferences/PreferencesKeys.kt @@ -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") diff --git a/data/onramp/src/main/java/com/tangem/data/onramp/DefaultOnrampRepository.kt b/data/onramp/src/main/java/com/tangem/data/onramp/DefaultOnrampRepository.kt index bdbb147344..55fcfc1077 100644 --- a/data/onramp/src/main/java/com/tangem/data/onramp/DefaultOnrampRepository.kt +++ b/data/onramp/src/main/java/com/tangem/data/onramp/DefaultOnrampRepository.kt @@ -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( + key = PreferencesKeys.ONRAMP_DEFAULT_CURRENCY, + value = currencyConverter.convertBack(currency), + ) + } + + override suspend fun getDefaultCurrencySync(): OnrampCurrency? { + return appPreferencesStore + .getObjectSyncOrNull(PreferencesKeys.ONRAMP_DEFAULT_CURRENCY) + ?.let(currencyConverter::convert) + } + + override fun getDefaultCurrency(): Flow { + return appPreferencesStore + .getObject(PreferencesKeys.ONRAMP_DEFAULT_CURRENCY) + .map { it?.let(currencyConverter::convert) } + } } \ No newline at end of file diff --git a/data/onramp/src/main/java/com/tangem/data/onramp/converters/CurrencyConverter.kt b/data/onramp/src/main/java/com/tangem/data/onramp/converters/CurrencyConverter.kt index 01e8ee6309..4c37be2fc8 100644 --- a/data/onramp/src/main/java/com/tangem/data/onramp/converters/CurrencyConverter.kt +++ b/data/onramp/src/main/java/com/tangem/data/onramp/converters/CurrencyConverter.kt @@ -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 { +internal class CurrencyConverter : TwoWayConverter { override fun convert(value: OnrampCurrencyDTO) = OnrampCurrency( name = value.name, @@ -12,4 +12,11 @@ internal class CurrencyConverter : Converter 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, + ) } \ No newline at end of file diff --git a/data/onramp/src/main/java/com/tangem/data/onramp/di/OnrampDataModule.kt b/data/onramp/src/main/java/com/tangem/data/onramp/di/OnrampDataModule.kt index c1b0d24e4b..d47543851a 100644 --- a/data/onramp/src/main/java/com/tangem/data/onramp/di/OnrampDataModule.kt +++ b/data/onramp/src/main/java/com/tangem/data/onramp/di/OnrampDataModule.kt @@ -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, ) } } \ No newline at end of file diff --git a/domain/onramp/models/src/main/kotlin/com/tangem/domain/onramp/model/OnrampCurrencies.kt b/domain/onramp/models/src/main/kotlin/com/tangem/domain/onramp/model/OnrampCurrencies.kt new file mode 100644 index 0000000000..1af69cd6ef --- /dev/null +++ b/domain/onramp/models/src/main/kotlin/com/tangem/domain/onramp/model/OnrampCurrencies.kt @@ -0,0 +1,3 @@ +package com.tangem.domain.onramp.model + +data class OnrampCurrencies(val populars: List, val others: List) \ No newline at end of file diff --git a/domain/onramp/src/main/java/com/tangem/domain/onramp/GetOnrampCurrenciesUseCase.kt b/domain/onramp/src/main/java/com/tangem/domain/onramp/GetOnrampCurrenciesUseCase.kt new file mode 100644 index 0000000000..b1206947db --- /dev/null +++ b/domain/onramp/src/main/java/com/tangem/domain/onramp/GetOnrampCurrenciesUseCase.kt @@ -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 { + 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") + } +} \ No newline at end of file diff --git a/domain/onramp/src/main/java/com/tangem/domain/onramp/OnrampSaveDefaultCurrencyUseCase.kt b/domain/onramp/src/main/java/com/tangem/domain/onramp/OnrampSaveDefaultCurrencyUseCase.kt new file mode 100644 index 0000000000..e30213f5ab --- /dev/null +++ b/domain/onramp/src/main/java/com/tangem/domain/onramp/OnrampSaveDefaultCurrencyUseCase.kt @@ -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) + } +} \ No newline at end of file diff --git a/domain/onramp/src/main/java/com/tangem/domain/onramp/repositories/OnrampRepository.kt b/domain/onramp/src/main/java/com/tangem/domain/onramp/repositories/OnrampRepository.kt index 8b251a9d47..73c41ce3db 100644 --- a/domain/onramp/src/main/java/com/tangem/domain/onramp/repositories/OnrampRepository.kt +++ b/domain/onramp/src/main/java/com/tangem/domain/onramp/repositories/OnrampRepository.kt @@ -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 suspend fun getCountries(): List + suspend fun saveDefaultCurrency(currency: OnrampCurrency) + suspend fun getDefaultCurrencySync(): OnrampCurrency? + fun getDefaultCurrency(): Flow } \ No newline at end of file diff --git a/features/onramp/impl/build.gradle.kts b/features/onramp/impl/build.gradle.kts index 1ff3b4c39c..be47292f09 100644 --- a/features/onramp/impl/build.gradle.kts +++ b/features/onramp/impl/build.gradle.kts @@ -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) diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/component/impl/DefaultConfirmResidencyComponent.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/component/impl/DefaultConfirmResidencyComponent.kt index a55688ca57..9d3406858d 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/component/impl/DefaultConfirmResidencyComponent.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/component/impl/DefaultConfirmResidencyComponent.kt @@ -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, diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/component/preview/PreviewConfirmResidencyComponent.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/component/preview/PreviewConfirmResidencyComponent.kt index b5e167f157..bb051a618e 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/component/preview/PreviewConfirmResidencyComponent.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/component/preview/PreviewConfirmResidencyComponent.kt @@ -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 -> diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcountry/DefaultSelectCountryComponent.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcountry/DefaultSelectCountryComponent.kt index 9ac777a7b4..60c7d65ff5 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcountry/DefaultSelectCountryComponent.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcountry/DefaultSelectCountryComponent.kt @@ -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, diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcountry/entity/CountryListUMController.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcountry/entity/CountryListUMController.kt index e91aa92aea..5956d4128d 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcountry/entity/CountryListUMController.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcountry/entity/CountryListUMController.kt @@ -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() diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcountry/entity/transformer/UpdateCountryItemsTransformer.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcountry/entity/transformer/UpdateCountryItemsTransformer.kt index 8af238ebf4..9b8e8b9f73 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcountry/entity/transformer/UpdateCountryItemsTransformer.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcountry/entity/transformer/UpdateCountryItemsTransformer.kt @@ -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, private val onQueryChange: (String) -> Unit, private val onActiveChange: (Boolean) -> Unit, -) { +) : Transformer { - fun transform(prevState: CountryListUM): CountryListUM { + override fun transform(prevState: CountryListUM): CountryListUM { val searchBarItem = prevState.getSearchBar() ?: createSearchBarItem() return prevState.copy( diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcurrency/DefaultSelectCurrencyComponent.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcurrency/DefaultSelectCurrencyComponent.kt new file mode 100644 index 0000000000..687df5d015 --- /dev/null +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcurrency/DefaultSelectCurrencyComponent.kt @@ -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 + } +} \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcurrency/SelectCurrencyComponent.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcurrency/SelectCurrencyComponent.kt new file mode 100644 index 0000000000..a6c69ab8f3 --- /dev/null +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcurrency/SelectCurrencyComponent.kt @@ -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 +} \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcurrency/di/OnrampSelectCurrencyComponentModule.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcurrency/di/OnrampSelectCurrencyComponentModule.kt new file mode 100644 index 0000000000..f8ccf16d35 --- /dev/null +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcurrency/di/OnrampSelectCurrencyComponentModule.kt @@ -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 +} \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcurrency/di/OnrampSelectCurrencyModelModule.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcurrency/di/OnrampSelectCurrencyModelModule.kt new file mode 100644 index 0000000000..3541b9cb45 --- /dev/null +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcurrency/di/OnrampSelectCurrencyModelModule.kt @@ -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 +} \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcurrency/entity/CurrenciesListUM.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcurrency/entity/CurrenciesListUM.kt new file mode 100644 index 0000000000..91916eb2c4 --- /dev/null +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcurrency/entity/CurrenciesListUM.kt @@ -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>, + ) : CurrenciesListUM + + data class Error(override val searchBarUM: SearchBarUM, val onRetry: () -> Unit) : CurrenciesListUM + + data class Content( + override val searchBarUM: SearchBarUM, + val sections: ImmutableList>, + ) : 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) + } + } +} \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcurrency/entity/CurrenciesSection.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcurrency/entity/CurrenciesSection.kt new file mode 100644 index 0000000000..b15dffbba1 --- /dev/null +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcurrency/entity/CurrenciesSection.kt @@ -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(val title: TextReference, val items: ImmutableList) \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcurrency/entity/CurrencyItemState.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcurrency/entity/CurrencyItemState.kt new file mode 100644 index 0000000000..0e3c11fe59 --- /dev/null +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcurrency/entity/CurrencyItemState.kt @@ -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 +} \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcurrency/entity/CurrencyListController.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcurrency/entity/CurrencyListController.kt new file mode 100644 index 0000000000..157e5a9133 --- /dev/null +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcurrency/entity/CurrencyListController.kt @@ -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 get() = _state.asStateFlow() + private val _state: MutableStateFlow = 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 = + MutableList(size) { index -> CurrencyItemState.Loading("$prefix#$index") }.toImmutableList() +} \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcurrency/entity/transformer/UpdateCurrencyItemsTransformer.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcurrency/entity/transformer/UpdateCurrencyItemsTransformer.kt new file mode 100644 index 0000000000..8e3782838b --- /dev/null +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcurrency/entity/transformer/UpdateCurrencyItemsTransformer.kt @@ -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, + private val query: String, + private val onRetry: () -> Unit, + private val onCurrencyClick: (OnrampCurrency) -> Unit, +) : Transformer { + + 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.filterByQuery(): List { + 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) }, + ) + } +} \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcurrency/model/OnrampSelectCurrencyModel.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcurrency/model/OnrampSelectCurrencyModel.kt new file mode 100644 index 0000000000..b6668e3f40 --- /dev/null +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcurrency/model/OnrampSelectCurrencyModel.kt @@ -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 get() = controller.state + + private val params: SelectCurrencyComponent.Params = paramsContainer.require() + private val controller = CurrencyListController(currencySearchBarUM = createSearchBarUM()) + private val refreshTrigger = MutableSharedFlow() + + 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, + ) + } +} \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcurrency/ui/SelectCurrencyBottomSheet.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcurrency/ui/SelectCurrencyBottomSheet.kt new file mode 100644 index 0000000000..88dfc1d6d9 --- /dev/null +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcurrency/ui/SelectCurrencyBottomSheet.kt @@ -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( + 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, + ) + } + } +} \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/utils/SearchManager.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/utils/SearchManager.kt index b47bbe1810..a2f80e0f27 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/utils/SearchManager.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/utils/SearchManager.kt @@ -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 }