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 index 29cd227fdd..f15fd1b0c0 100644 --- a/app/src/main/java/com/tangem/tap/di/domain/OnrampDomainModule.kt +++ b/app/src/main/java/com/tangem/tap/di/domain/OnrampDomainModule.kt @@ -41,4 +41,10 @@ internal object OnrampDomainModule { fun provideOnrampSaveDefaultCountryUseCase(onrampRepository: OnrampRepository): OnrampSaveDefaultCountryUseCase { return OnrampSaveDefaultCountryUseCase(onrampRepository) } + + @Provides + @Singleton + fun provideCheckOnrampAvailabilityUseCase(onrampRepository: OnrampRepository): CheckOnrampAvailabilityUseCase { + return CheckOnrampAvailabilityUseCase(onrampRepository) + } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/TradeCryptoMiddleware.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/TradeCryptoMiddleware.kt index a21351a6b9..b68935424e 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/TradeCryptoMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/TradeCryptoMiddleware.kt @@ -55,11 +55,15 @@ object TradeCryptoMiddleware { private fun proceedBuyAction(state: () -> AppState?, action: TradeCryptoAction.Buy) { val isOnrampEnabled = store.inject(DaggerGraphState::onrampFeatureToggles).isFeatureEnabled - if (isOnrampEnabled) proceedWithOnramp() else proceedWithLegacyBuyAction(state, action) + if (isOnrampEnabled) { + proceedWithOnramp(action.cryptoCurrencyStatus.currency) + } else { + proceedWithLegacyBuyAction(state, action) + } } - private fun proceedWithOnramp() { - store.dispatchNavigationAction { push(AppRoute.Onramp) } + private fun proceedWithOnramp(cryptoCurrency: CryptoCurrency) { + store.dispatchNavigationAction { push(AppRoute.Onramp(cryptoCurrency)) } } private fun proceedWithLegacyBuyAction(state: () -> AppState?, action: TradeCryptoAction.Buy) { diff --git a/app/src/main/java/com/tangem/tap/routing/utils/ChildFactory.kt b/app/src/main/java/com/tangem/tap/routing/utils/ChildFactory.kt index e53226e658..8678616017 100644 --- a/app/src/main/java/com/tangem/tap/routing/utils/ChildFactory.kt +++ b/app/src/main/java/com/tangem/tap/routing/utils/ChildFactory.kt @@ -199,7 +199,7 @@ internal class ChildFactory @Inject constructor( is AppRoute.Onramp -> { route.asComponentChild( contextProvider = contextProvider(route, contextFactory), - params = OnrampComponent.Params(), + params = OnrampComponent.Params(route.currency.name), componentFactory = onrampComponentFactory, ) } diff --git a/common/routing/src/main/kotlin/com/tangem/common/routing/AppRoute.kt b/common/routing/src/main/kotlin/com/tangem/common/routing/AppRoute.kt index f2903ad378..66993b210a 100644 --- a/common/routing/src/main/kotlin/com/tangem/common/routing/AppRoute.kt +++ b/common/routing/src/main/kotlin/com/tangem/common/routing/AppRoute.kt @@ -295,7 +295,9 @@ sealed class AppRoute(val path: String) : Route { } @Serializable - data object Onramp : AppRoute(path = "/onramp") + data class Onramp(val currency: CryptoCurrency) : AppRoute(path = "/onramp/${currency.symbol}"), RouteBundleParams { + override fun getBundle(): Bundle = bundle(serializer()) + } @Serializable data class BuyCrypto( diff --git a/domain/onramp/models/src/main/kotlin/com/tangem/domain/onramp/model/OnrampAvailability.kt b/domain/onramp/models/src/main/kotlin/com/tangem/domain/onramp/model/OnrampAvailability.kt new file mode 100644 index 0000000000..8113348d12 --- /dev/null +++ b/domain/onramp/models/src/main/kotlin/com/tangem/domain/onramp/model/OnrampAvailability.kt @@ -0,0 +1,8 @@ +package com.tangem.domain.onramp.model + +sealed interface OnrampAvailability { + + data object Available : OnrampAvailability + data class NotSupported(val country: OnrampCountry) : OnrampAvailability + data class ConfirmResidency(val country: OnrampCountry) : OnrampAvailability +} \ No newline at end of file diff --git a/domain/onramp/models/src/main/kotlin/com/tangem/domain/onramp/model/OnrampCountry.kt b/domain/onramp/models/src/main/kotlin/com/tangem/domain/onramp/model/OnrampCountry.kt index 1a3a9b69dc..a430172a2e 100644 --- a/domain/onramp/models/src/main/kotlin/com/tangem/domain/onramp/model/OnrampCountry.kt +++ b/domain/onramp/models/src/main/kotlin/com/tangem/domain/onramp/model/OnrampCountry.kt @@ -1,5 +1,8 @@ package com.tangem.domain.onramp.model +import kotlinx.serialization.Serializable + +@Serializable data class OnrampCountry( val id: String, val name: String, diff --git a/domain/onramp/models/src/main/kotlin/com/tangem/domain/onramp/model/OnrampCurrency.kt b/domain/onramp/models/src/main/kotlin/com/tangem/domain/onramp/model/OnrampCurrency.kt index 7f8fd42c20..4f3d44f222 100644 --- a/domain/onramp/models/src/main/kotlin/com/tangem/domain/onramp/model/OnrampCurrency.kt +++ b/domain/onramp/models/src/main/kotlin/com/tangem/domain/onramp/model/OnrampCurrency.kt @@ -1,5 +1,8 @@ package com.tangem.domain.onramp.model +import kotlinx.serialization.Serializable + +@Serializable data class OnrampCurrency( val name: String, val code: String, diff --git a/domain/onramp/src/main/java/com/tangem/domain/onramp/CheckOnrampAvailabilityUseCase.kt b/domain/onramp/src/main/java/com/tangem/domain/onramp/CheckOnrampAvailabilityUseCase.kt new file mode 100644 index 0000000000..c2aa371b07 --- /dev/null +++ b/domain/onramp/src/main/java/com/tangem/domain/onramp/CheckOnrampAvailabilityUseCase.kt @@ -0,0 +1,24 @@ +package com.tangem.domain.onramp + +import arrow.core.Either +import com.tangem.domain.onramp.model.OnrampAvailability +import com.tangem.domain.onramp.repositories.OnrampRepository + +class CheckOnrampAvailabilityUseCase(private val repository: OnrampRepository) { + + suspend operator fun invoke(): Either { + return Either.catch { + return@catch OnrampAvailability.Available + repository.getDefaultCountrySync()?.let { savedCountry -> + return@catch if (savedCountry.onrampAvailable) { + OnrampAvailability.Available + } else { + OnrampAvailability.NotSupported(savedCountry) + } + } + + val detectedCountry = repository.getCountryByIp() + OnrampAvailability.ConfirmResidency(detectedCountry) + } + } +} \ No newline at end of file diff --git a/features/onramp/api/src/main/kotlin/com/tangem/features/onramp/component/OnrampComponent.kt b/features/onramp/api/src/main/kotlin/com/tangem/features/onramp/component/OnrampComponent.kt index 16ac6b24f7..219536ddb7 100644 --- a/features/onramp/api/src/main/kotlin/com/tangem/features/onramp/component/OnrampComponent.kt +++ b/features/onramp/api/src/main/kotlin/com/tangem/features/onramp/component/OnrampComponent.kt @@ -5,7 +5,7 @@ import com.tangem.core.ui.decompose.ComposableContentComponent interface OnrampComponent : ComposableContentComponent { - class Params + data class Params(val currency: String) interface Factory : ComponentFactory } \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/confirmresidency/ConfirmResidencyComponent.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/confirmresidency/ConfirmResidencyComponent.kt index bae3a5be6e..dd8bd0a162 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/confirmresidency/ConfirmResidencyComponent.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/confirmresidency/ConfirmResidencyComponent.kt @@ -2,15 +2,11 @@ package com.tangem.features.onramp.confirmresidency import com.tangem.core.decompose.factory.ComponentFactory import com.tangem.core.ui.decompose.ComposableBottomSheetComponent +import com.tangem.domain.onramp.model.OnrampCountry internal interface ConfirmResidencyComponent : ComposableBottomSheetComponent { - data class Params( - val countryName: String, - val isOnrampSupported: Boolean, - val countryFlagUrl: String, - val onDismiss: () -> Unit, - ) + data class Params(val country: OnrampCountry, val onDismiss: (OnrampCountry) -> Unit) interface Factory : ComponentFactory } \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/confirmresidency/DefaultConfirmResidencyComponent.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/confirmresidency/DefaultConfirmResidencyComponent.kt index fa72ff4f67..25e5bce171 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/confirmresidency/DefaultConfirmResidencyComponent.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/confirmresidency/DefaultConfirmResidencyComponent.kt @@ -38,7 +38,7 @@ internal class DefaultConfirmResidencyComponent @AssistedInject constructor( ) override fun dismiss() { - params.onDismiss() + router.pop() } @Composable diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/confirmresidency/model/ConfirmResidencyModel.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/confirmresidency/model/ConfirmResidencyModel.kt index 44a4678bbe..2b0f91f3d1 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/confirmresidency/model/ConfirmResidencyModel.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/confirmresidency/model/ConfirmResidencyModel.kt @@ -5,18 +5,24 @@ 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 +import com.tangem.core.decompose.navigation.Router import com.tangem.core.ui.extensions.resourceReference +import com.tangem.domain.onramp.GetOnrampCountryUseCase import com.tangem.features.onramp.confirmresidency.ConfirmResidencyComponent import com.tangem.features.onramp.confirmresidency.entity.ConfirmResidencyUM import com.tangem.features.onramp.impl.R import com.tangem.features.onramp.entity.ConfirmResidencyBottomSheetConfig import com.tangem.utils.coroutines.CoroutineDispatcherProvider import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach import javax.inject.Inject @ComponentScoped internal class ConfirmResidencyModel @Inject constructor( override val dispatchers: CoroutineDispatcherProvider, + private val router: Router, + getOnrampCountryUseCase: GetOnrampCountryUseCase, paramsContainer: ParamsContainer, ) : Model() { @@ -24,9 +30,9 @@ internal class ConfirmResidencyModel @Inject constructor( val bottomSheetNavigation: SlotNavigation = SlotNavigation() val state: MutableStateFlow = MutableStateFlow( ConfirmResidencyUM( - country = params.countryName, - countryFlagUrl = params.countryFlagUrl, - isCountrySupported = params.isOnrampSupported, + country = params.country.name, + countryFlagUrl = params.country.image, + isCountrySupported = params.country.onrampAvailable, primaryButtonConfig = getPrimaryButtonConfig(), secondaryButtonConfig = ConfirmResidencyUM.ActionButtonConfig( onClick = ::onChangeClick, @@ -35,22 +41,29 @@ internal class ConfirmResidencyModel @Inject constructor( ), ) - private fun getPrimaryButtonConfig() = if (params.isOnrampSupported) { + init { + getOnrampCountryUseCase.invoke() + .onEach { maybeCountry -> + val country = maybeCountry.getOrNull() + if (country != null) { + params.onDismiss(country) + } + } + .launchIn(modelScope) + } + + private fun getPrimaryButtonConfig() = if (params.country.onrampAvailable) { ConfirmResidencyUM.ActionButtonConfig( - onClick = params.onDismiss, + onClick = { params.onDismiss(params.country) }, text = resourceReference(R.string.common_confirm), ) } else { ConfirmResidencyUM.ActionButtonConfig( - onClick = ::onCloseClick, + onClick = router::pop, text = resourceReference(R.string.common_close), ) } - @Suppress("EmptyFunctionBlock") - private fun onCloseClick() { - } - private fun onChangeClick() { bottomSheetNavigation.activate(ConfirmResidencyBottomSheetConfig.SelectCountry) } diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/DefaultOnrampMainComponent.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/DefaultOnrampMainComponent.kt new file mode 100644 index 0000000000..be1d4dcb81 --- /dev/null +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/DefaultOnrampMainComponent.kt @@ -0,0 +1,66 @@ +package com.tangem.features.onramp.main + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.ui.Modifier +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.decompose.ComposableBottomSheetComponent +import com.tangem.features.onramp.confirmresidency.ConfirmResidencyComponent +import com.tangem.features.onramp.main.entity.OnrampMainBottomSheetConfig +import com.tangem.features.onramp.main.model.OnrampMainComponentModel +import com.tangem.features.onramp.main.ui.OnrampMainComponentContent +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject + +internal class DefaultOnrampMainComponent @AssistedInject constructor( + @Assisted appComponentContext: AppComponentContext, + @Assisted params: OnrampMainComponent.Params, + private val confirmResidencyComponentFactory: ConfirmResidencyComponent.Factory, +) : OnrampMainComponent, AppComponentContext by appComponentContext { + + private val model: OnrampMainComponentModel = getOrCreateModel(params) + private val bottomSheetSlot = childSlot( + source = model.bottomSheetNavigation, + serializer = null, + handleBackButton = false, + childFactory = ::bottomSheetChild, + ) + + @Composable + override fun Content(modifier: Modifier) { + val state by model.state.collectAsState() + val bottomSheet by bottomSheetSlot.subscribeAsState() + + OnrampMainComponentContent(modifier = modifier, state = state) + bottomSheet.child?.instance?.BottomSheet() + } + + private fun bottomSheetChild( + config: OnrampMainBottomSheetConfig, + componentContext: ComponentContext, + ): ComposableBottomSheetComponent = when (config) { + is OnrampMainBottomSheetConfig.ConfirmResidency -> confirmResidencyComponentFactory.create( + context = childByContext(componentContext), + params = ConfirmResidencyComponent.Params( + country = config.country, + onDismiss = { model.bottomSheetNavigation.dismiss() }, + ), + ) + } + + @AssistedFactory + interface Factory : OnrampMainComponent.Factory { + override fun create( + context: AppComponentContext, + params: OnrampMainComponent.Params, + ): DefaultOnrampMainComponent + } +} \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/OnrampMainComponent.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/OnrampMainComponent.kt new file mode 100644 index 0000000000..22592a4268 --- /dev/null +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/OnrampMainComponent.kt @@ -0,0 +1,11 @@ +package com.tangem.features.onramp.main + +import com.tangem.core.decompose.factory.ComponentFactory +import com.tangem.core.ui.decompose.ComposableContentComponent + +internal interface OnrampMainComponent : ComposableContentComponent { + + class Params(val currency: String, val openSettings: () -> Unit) + + interface Factory : ComponentFactory +} \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/di/OnrampMainComponentModelModule.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/di/OnrampMainComponentModelModule.kt new file mode 100644 index 0000000000..99826bae5d --- /dev/null +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/di/OnrampMainComponentModelModule.kt @@ -0,0 +1,20 @@ +package com.tangem.features.onramp.main.di + +import com.tangem.core.decompose.di.DecomposeComponent +import com.tangem.core.decompose.model.Model +import com.tangem.features.onramp.main.model.OnrampMainComponentModel +import dagger.Binds +import dagger.Module +import dagger.hilt.InstallIn +import dagger.multibindings.ClassKey +import dagger.multibindings.IntoMap + +@Module +@InstallIn(DecomposeComponent::class) +internal interface OnrampMainComponentModelModule { + + @Binds + @IntoMap + @ClassKey(OnrampMainComponentModel::class) + fun bindOnrampSelectCountryModel(model: OnrampMainComponentModel): Model +} \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/di/OnrampMainComponentModule.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/di/OnrampMainComponentModule.kt new file mode 100644 index 0000000000..78140491e5 --- /dev/null +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/di/OnrampMainComponentModule.kt @@ -0,0 +1,18 @@ +package com.tangem.features.onramp.main.di + +import com.tangem.features.onramp.main.DefaultOnrampMainComponent +import com.tangem.features.onramp.main.OnrampMainComponent +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 OnrampMainComponentModule { + + @Binds + @Singleton + fun bindOnrampMainComponentFactory(factory: DefaultOnrampMainComponent.Factory): OnrampMainComponent.Factory +} \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/entity/OnrampMainBottomSheetConfig.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/entity/OnrampMainBottomSheetConfig.kt new file mode 100644 index 0000000000..03c2357074 --- /dev/null +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/entity/OnrampMainBottomSheetConfig.kt @@ -0,0 +1,10 @@ +package com.tangem.features.onramp.main.entity + +import com.tangem.domain.onramp.model.OnrampCountry +import kotlinx.serialization.Serializable + +@Serializable +internal sealed interface OnrampMainBottomSheetConfig { + @Serializable + data class ConfirmResidency(val country: OnrampCountry) : OnrampMainBottomSheetConfig +} \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/entity/OnrampMainComponentUM.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/entity/OnrampMainComponentUM.kt new file mode 100644 index 0000000000..91f5ee356e --- /dev/null +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/entity/OnrampMainComponentUM.kt @@ -0,0 +1,54 @@ +package com.tangem.features.onramp.main.entity + +import androidx.compose.runtime.Immutable +import com.tangem.core.ui.components.appbar.models.TopAppBarButtonUM +import com.tangem.core.ui.extensions.TextReference +import com.tangem.core.ui.extensions.combinedReference +import com.tangem.core.ui.extensions.resourceReference +import com.tangem.core.ui.extensions.stringReference +import com.tangem.features.onramp.impl.R + +@Immutable +internal sealed interface OnrampMainComponentUM { + + val topBarConfig: OnrampMainTopBarUM + val buyButtonConfig: BuyButtonConfig + + data class InitialLoading( + val currency: String, + val onClose: () -> Unit, + val openSettings: () -> Unit, + val onBuyClick: () -> Unit, + ) : OnrampMainComponentUM { + override val topBarConfig: OnrampMainTopBarUM = OnrampMainTopBarUM( + title = combinedReference(resourceReference(R.string.common_buy), stringReference(" $currency")), + startButtonUM = TopAppBarButtonUM( + iconRes = R.drawable.ic_close_24, + onIconClicked = onClose, + enabled = true, + ), + endButtonUM = TopAppBarButtonUM( + iconRes = R.drawable.ic_more_vertical_24, + onIconClicked = openSettings, + enabled = false, + ), + ) + + override val buyButtonConfig: BuyButtonConfig = BuyButtonConfig( + text = resourceReference(R.string.common_buy), + onClick = onBuyClick, + enabled = false, + ) + } + + data class Content( + override val topBarConfig: OnrampMainTopBarUM, + override val buyButtonConfig: BuyButtonConfig, + ) : OnrampMainComponentUM +} + +internal data class BuyButtonConfig( + val text: TextReference, + val onClick: () -> Unit, + val enabled: Boolean, +) \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/entity/OnrampMainTopBarUM.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/entity/OnrampMainTopBarUM.kt new file mode 100644 index 0000000000..997292dfb8 --- /dev/null +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/entity/OnrampMainTopBarUM.kt @@ -0,0 +1,10 @@ +package com.tangem.features.onramp.main.entity + +import com.tangem.core.ui.components.appbar.models.TopAppBarButtonUM +import com.tangem.core.ui.extensions.TextReference + +data class OnrampMainTopBarUM( + val title: TextReference, + val startButtonUM: TopAppBarButtonUM, + val endButtonUM: TopAppBarButtonUM, +) \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/entity/transformer/OnrampAvailabilityTransformer.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/entity/transformer/OnrampAvailabilityTransformer.kt new file mode 100644 index 0000000000..9da2af4521 --- /dev/null +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/entity/transformer/OnrampAvailabilityTransformer.kt @@ -0,0 +1,53 @@ +package com.tangem.features.onramp.main.entity.transformer + +import arrow.core.Either +import com.tangem.domain.onramp.model.OnrampAvailability +import com.tangem.domain.onramp.model.OnrampCountry +import com.tangem.features.onramp.main.entity.OnrampMainBottomSheetConfig +import com.tangem.features.onramp.main.entity.OnrampMainComponentUM +import com.tangem.utils.transformer.Transformer +import timber.log.Timber + +internal class OnrampAvailabilityTransformer( + private val maybeAvailabilityStatus: Either, + private val openBottomSheet: (OnrampMainBottomSheetConfig) -> Unit, +) : Transformer { + + override fun transform(prevState: OnrampMainComponentUM): OnrampMainComponentUM { + return maybeAvailabilityStatus.fold( + ifLeft = { + Timber.e(it) + prevState + }, + ifRight = { availability -> + when (availability) { + OnrampAvailability.Available -> handleAvailableStatus(prevState) + is OnrampAvailability.ConfirmResidency -> handleShowingConfirmResidency( + prevState = prevState, + country = availability.country, + ) + is OnrampAvailability.NotSupported -> handleShowingConfirmResidency( + prevState = prevState, + country = availability.country, + ) + } + }, + ) + } + + private fun handleAvailableStatus(prevState: OnrampMainComponentUM): OnrampMainComponentUM { + val endButton = prevState.topBarConfig.endButtonUM.copy(enabled = true) + return OnrampMainComponentUM.Content( + topBarConfig = prevState.topBarConfig.copy(endButtonUM = endButton), + buyButtonConfig = prevState.buyButtonConfig, + ) + } + + private fun handleShowingConfirmResidency( + prevState: OnrampMainComponentUM, + country: OnrampCountry, + ): OnrampMainComponentUM { + openBottomSheet.invoke(OnrampMainBottomSheetConfig.ConfirmResidency(country)) + return prevState + } +} \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/model/OnrampMainComponentModel.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/model/OnrampMainComponentModel.kt new file mode 100644 index 0000000000..97f7c33854 --- /dev/null +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/model/OnrampMainComponentModel.kt @@ -0,0 +1,65 @@ +package com.tangem.features.onramp.main.model + +import com.arkivanov.decompose.router.slot.SlotNavigation +import com.arkivanov.decompose.router.slot.activate +import com.tangem.core.decompose.model.Model +import com.tangem.core.decompose.model.ParamsContainer +import com.tangem.core.decompose.navigation.Router +import com.tangem.domain.onramp.CheckOnrampAvailabilityUseCase +import com.tangem.features.onramp.main.OnrampMainComponent +import com.tangem.features.onramp.main.entity.OnrampMainBottomSheetConfig +import com.tangem.features.onramp.main.entity.OnrampMainComponentUM +import com.tangem.features.onramp.main.entity.transformer.OnrampAvailabilityTransformer +import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import com.tangem.utils.transformer.Transformer +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.update +import kotlinx.coroutines.launch +import javax.inject.Inject + +internal class OnrampMainComponentModel @Inject constructor( + override val dispatchers: CoroutineDispatcherProvider, + private val router: Router, + private val checkOnrampAvailabilityUseCase: CheckOnrampAvailabilityUseCase, + paramsContainer: ParamsContainer, +) : Model() { + + private val params: OnrampMainComponent.Params = paramsContainer.require() + private val _state: MutableStateFlow = MutableStateFlow( + value = OnrampMainComponentUM.InitialLoading( + currency = params.currency, + onClose = router::pop, + openSettings = params.openSettings, + onBuyClick = ::onBuyClick, + ), + ) + val state: StateFlow get() = _state.asStateFlow() + val bottomSheetNavigation: SlotNavigation = SlotNavigation() + + init { + checkResidenceCountry() + } + + private fun checkResidenceCountry() { + modelScope.launch { + OnrampAvailabilityTransformer( + maybeAvailabilityStatus = checkOnrampAvailabilityUseCase.invoke(), + openBottomSheet = ::openBottomSheet, + ).let(::updateState) + } + } + + @Suppress("EmptyFunctionBlock") + private fun onBuyClick() { + } + + private fun openBottomSheet(config: OnrampMainBottomSheetConfig) { + bottomSheetNavigation.activate(config) + } + + private fun updateState(transformer: Transformer) { + _state.update(transformer::transform) + } +} \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/ui/OnrampMainComponentContent.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/ui/OnrampMainComponentContent.kt new file mode 100644 index 0000000000..bf30582c07 --- /dev/null +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/ui/OnrampMainComponentContent.kt @@ -0,0 +1,100 @@ +package com.tangem.features.onramp.main.ui + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.FabPosition +import androidx.compose.material3.Scaffold +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.res.stringResource +import com.tangem.core.ui.components.CircleShimmer +import com.tangem.core.ui.components.PrimaryButton +import com.tangem.core.ui.components.RectangleShimmer +import com.tangem.core.ui.components.appbar.TangemTopAppBar +import com.tangem.core.ui.extensions.resolveReference +import com.tangem.core.ui.res.TangemTheme +import com.tangem.core.ui.utils.WindowInsetsZero +import com.tangem.features.onramp.impl.R +import com.tangem.features.onramp.main.entity.OnrampMainComponentUM + +@Composable +internal fun OnrampMainComponentContent(state: OnrampMainComponentUM, modifier: Modifier = Modifier) { + Scaffold( + modifier = modifier, + contentWindowInsets = WindowInsetsZero, + containerColor = TangemTheme.colors.background.secondary, + topBar = { + TangemTopAppBar( + modifier = Modifier.statusBarsPadding(), + startButton = state.topBarConfig.startButtonUM, + endButton = state.topBarConfig.endButtonUM, + title = state.topBarConfig.title.resolveReference(), + ) + }, + content = { innerPadding -> + val contentModifier = Modifier + .padding(innerPadding) + .padding(horizontal = TangemTheme.dimens.spacing16) + .fillMaxWidth() + .wrapContentHeight() + when (state) { + is OnrampMainComponentUM.InitialLoading -> InitialLoading(modifier = contentModifier) + is OnrampMainComponentUM.Content -> Content(modifier = contentModifier) + } + }, + floatingActionButton = { + BuyButton( + modifier = Modifier + .navigationBarsPadding() + .padding(horizontal = TangemTheme.dimens.spacing16) + .fillMaxWidth(), + onClick = state.buyButtonConfig.onClick, + enabled = state.buyButtonConfig.enabled, + ) + }, + floatingActionButtonPosition = FabPosition.Center, + ) +} + +@Composable +private fun InitialLoading(modifier: Modifier = Modifier) { + Column( + modifier = modifier + .clip(shape = RoundedCornerShape(size = TangemTheme.dimens.radius16)) + .background(TangemTheme.colors.background.action) + .padding(vertical = TangemTheme.dimens.spacing28), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + CircleShimmer(modifier = Modifier.size(TangemTheme.dimens.size40)) + RectangleShimmer( + modifier = Modifier + .padding(top = TangemTheme.dimens.spacing16) + .size(width = TangemTheme.dimens.size96, height = TangemTheme.dimens.size24), + radius = TangemTheme.dimens.radius3, + ) + RectangleShimmer( + modifier = Modifier + .padding(top = TangemTheme.dimens.spacing16) + .size(width = TangemTheme.dimens.size72, height = TangemTheme.dimens.size12), + radius = TangemTheme.dimens.radius3, + ) + } +} + +@Composable +private fun Content(modifier: Modifier = Modifier) { + Box(modifier = modifier) +} + +@Composable +private fun BuyButton(onClick: () -> Unit, enabled: Boolean, modifier: Modifier = Modifier) { + PrimaryButton( + modifier = modifier, + text = stringResource(id = R.string.common_buy), + onClick = onClick, + enabled = enabled, + ) +} \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/root/DefaultOnrampComponent.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/root/DefaultOnrampComponent.kt index c65a434c7a..16d046651d 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/root/DefaultOnrampComponent.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/root/DefaultOnrampComponent.kt @@ -1,5 +1,6 @@ package com.tangem.features.onramp.root +import androidx.activity.compose.BackHandler import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier @@ -9,10 +10,13 @@ import com.arkivanov.decompose.extensions.compose.jetpack.stack.animation.stackA import com.arkivanov.decompose.extensions.compose.jetpack.subscribeAsState import com.arkivanov.decompose.router.stack.StackNavigation import com.arkivanov.decompose.router.stack.childStack +import com.arkivanov.decompose.router.stack.pop +import com.arkivanov.decompose.router.stack.push import com.tangem.core.decompose.context.AppComponentContext import com.tangem.core.decompose.context.childByContext import com.tangem.core.ui.decompose.ComposableContentComponent import com.tangem.features.onramp.component.OnrampComponent +import com.tangem.features.onramp.main.OnrampMainComponent import com.tangem.features.onramp.root.entity.OnrampChild import com.tangem.features.onramp.settings.OnrampSettingsComponent import dagger.assisted.Assisted @@ -22,8 +26,9 @@ import dagger.assisted.AssistedInject @Suppress("UnusedPrivateMember") internal class DefaultOnrampComponent @AssistedInject constructor( @Assisted context: AppComponentContext, - @Assisted params: OnrampComponent.Params, + @Assisted private val params: OnrampComponent.Params, private val settingsComponentFactory: OnrampSettingsComponent.Factory, + private val onrampMainComponentFactory: OnrampMainComponent.Factory, ) : OnrampComponent, AppComponentContext by context { private val navigation = StackNavigation() @@ -31,7 +36,7 @@ internal class DefaultOnrampComponent @AssistedInject constructor( key = "onramp_content_stack", source = navigation, serializer = OnrampChild.serializer(), - initialConfiguration = OnrampChild.Settings, + initialConfiguration = OnrampChild.Main, handleBackButton = false, childFactory = ::screenChild, ) @@ -40,6 +45,7 @@ internal class DefaultOnrampComponent @AssistedInject constructor( override fun Content(modifier: Modifier) { val childStack by contentStack.subscribeAsState() + BackHandler(onBack = router::pop) Children( stack = childStack, animation = stackAnimation(), @@ -52,10 +58,15 @@ internal class DefaultOnrampComponent @AssistedInject constructor( when (config) { OnrampChild.Settings -> settingsComponentFactory.create( context = childByContext(componentContext), - // params = OnrampSettingsComponent.Params(navigation::pop) // todo uncomment after [REDACTED_JIRA] - params = OnrampSettingsComponent.Params(router::pop), + params = OnrampSettingsComponent.Params(navigation::pop), + ) + OnrampChild.Main -> onrampMainComponentFactory.create( + context = childByContext(componentContext), + params = OnrampMainComponent.Params( + currency = params.currency, + openSettings = { navigation.push(OnrampChild.Settings) }, + ), ) - OnrampChild.Main -> TODO("[REDACTED_JIRA]") } @AssistedFactory