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 2f26b2a39a..a21351a6b9 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 @@ -54,6 +54,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) + } + + private fun proceedWithOnramp() { + store.dispatchNavigationAction { push(AppRoute.Onramp) } + } + + private fun proceedWithLegacyBuyAction(state: () -> AppState?, action: TradeCryptoAction.Buy) { val networkAddress = action.cryptoCurrencyStatus.value.networkAddress ?.defaultAddress ?.let(NetworkAddress.Address::value) 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 04e0e5f804..cca2474fa1 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 @@ -11,6 +11,7 @@ import com.tangem.features.disclaimer.api.components.DisclaimerComponent import com.tangem.features.managetokens.component.ManageTokensComponent import com.tangem.features.managetokens.component.ManageTokensSource import com.tangem.features.markets.details.MarketsTokenDetailsComponent +import com.tangem.features.onramp.component.OnrampComponent import com.tangem.features.pushnotifications.api.navigation.PushNotificationsRouter import com.tangem.features.send.api.navigation.SendRouter import com.tangem.features.staking.api.navigation.StakingRouter @@ -46,6 +47,7 @@ internal class ChildFactory @Inject constructor( private val disclaimerComponentFactory: DisclaimerComponent.Factory, private val manageTokensComponentFactory: ManageTokensComponent.Factory, private val marketsTokenDetailsComponentFactory: MarketsTokenDetailsComponent.Factory, + private val onrampComponentFactory: OnrampComponent.Factory, private val sendRouter: SendRouter, private val tokenDetailsRouter: TokenDetailsRouter, private val walletRouter: WalletRouter, @@ -186,6 +188,13 @@ internal class ChildFactory @Inject constructor( componentFactory = marketsTokenDetailsComponentFactory, ) } + is AppRoute.Onramp -> { + route.asComponentChild( + contextProvider = contextProvider(route, contextFactory), + params = OnrampComponent.Params(), + 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 398cb634a8..94e8f8487c 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 @@ -288,4 +288,7 @@ sealed class AppRoute(val path: String) : Route { val source: String, ) } + + @Serializable + data object Onramp : AppRoute(path = "/onramp") } \ No newline at end of file diff --git a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/entity/item/CurrencyNetworkUM.kt b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/entity/item/CurrencyNetworkUM.kt index bdec914faa..fbe35f92e4 100644 --- a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/entity/item/CurrencyNetworkUM.kt +++ b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/entity/item/CurrencyNetworkUM.kt @@ -14,6 +14,4 @@ internal data class CurrencyNetworkUM( ) : SelectableItemUM { override val id: String = network.id.value - - data class LongTapConfig(val contractAddress: String, val onLongTap: () -> Unit) } \ No newline at end of file diff --git a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/ui/CustomTokenSelectorContent.kt b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/ui/CustomTokenSelectorContent.kt index 21f3daf5e1..2fed1003f0 100644 --- a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/ui/CustomTokenSelectorContent.kt +++ b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/ui/CustomTokenSelectorContent.kt @@ -285,6 +285,18 @@ private class CustomTokenNetworkSelectorComponentPreviewProvider : onDerivationPathSelected = {}, ), ), + PreviewCustomTokenSelectorComponent( + params = CustomTokenSelectorComponent.Params.NetworkSelector( + userWalletId = UserWalletId(stringValue = "321"), + selectedNetwork = SelectedNetwork( + id = Network.ID(value = "0"), + name = "Ethereum", + derivationPath = Network.DerivationPath.Card("m/44'/0'/0'/0/0"), + canHandleTokens = true, + ), + onNetworkSelected = {}, + ), + ), ) } // endregion Preview \ 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 b5cd6141fb..16ac6b24f7 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,5 +5,7 @@ import com.tangem.core.ui.decompose.ComposableContentComponent interface OnrampComponent : ComposableContentComponent { - interface Factory : ComponentFactory + class Params + + interface Factory : ComponentFactory } \ No newline at end of file diff --git a/features/onramp/impl/build.gradle.kts b/features/onramp/impl/build.gradle.kts index 37ef2d093d..94b9c2785a 100644 --- a/features/onramp/impl/build.gradle.kts +++ b/features/onramp/impl/build.gradle.kts @@ -1,6 +1,7 @@ plugins { alias(deps.plugins.android.library) alias(deps.plugins.kotlin.android) + alias(deps.plugins.kotlin.serialization) alias(deps.plugins.kotlin.kapt) alias(deps.plugins.hilt.android) id("configuration") @@ -23,11 +24,11 @@ dependencies { implementation(deps.hilt.android) kapt(deps.hilt.kapt) - /* AndroidX */ + /** AndroidX */ implementation(deps.androidx.activity.compose) implementation(deps.lifecycle.compose) - /* Compose */ + /** Compose */ implementation(deps.compose.ui) implementation(deps.compose.ui.tooling) implementation(deps.compose.accompanist.systemUiController) @@ -35,4 +36,8 @@ dependencies { implementation(deps.compose.material3) implementation(deps.compose.shimmer) implementation(deps.compose.coil) + implementation(deps.compose.material) + + /** Other */ + implementation(deps.decompose.ext.compose) } \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/component/ConfirmResidencyComponent.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/component/ConfirmResidencyComponent.kt new file mode 100644 index 0000000000..74742aa575 --- /dev/null +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/component/ConfirmResidencyComponent.kt @@ -0,0 +1,16 @@ +package com.tangem.features.onramp.component + +import com.tangem.core.decompose.factory.ComponentFactory +import com.tangem.core.ui.decompose.ComposableBottomSheetComponent + +internal interface ConfirmResidencyComponent : ComposableBottomSheetComponent { + + data class Params( + val countryName: String, + val isOnrampSupported: Boolean, + val countryFlagUrl: String, + 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/component/impl/DefaultConfirmResidencyComponent.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/component/impl/DefaultConfirmResidencyComponent.kt new file mode 100644 index 0000000000..177e697eb8 --- /dev/null +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/component/impl/DefaultConfirmResidencyComponent.kt @@ -0,0 +1,55 @@ +package com.tangem.features.onramp.component.impl + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +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.component.ConfirmResidencyComponent +import com.tangem.features.onramp.model.ConfirmResidencyModel +import com.tangem.features.onramp.ui.ConfirmResidencyBottomSheet +import com.tangem.features.onramp.ui.ConfirmResidencyBottomSheetContent +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject + +internal class DefaultConfirmResidencyComponent @AssistedInject constructor( + @Assisted context: AppComponentContext, + @Assisted private val params: ConfirmResidencyComponent.Params, +) : ConfirmResidencyComponent, AppComponentContext by context { + + private val model: ConfirmResidencyModel = getOrCreateModel(params) + + override fun dismiss() { + params.onDismiss() + } + + @Composable + override fun BottomSheet() { + val state by model.state.collectAsStateWithLifecycle() + val bottomSheetConfig = remember { + TangemBottomSheetConfig( + isShow = true, + onDismissRequest = ::dismiss, + content = TangemBottomSheetConfigContent.Empty, + ) + } + ConfirmResidencyBottomSheet( + config = bottomSheetConfig, + content = { modifier -> + ConfirmResidencyBottomSheetContent(model = state, modifier = modifier) + }, + ) + } + + @AssistedFactory + interface Factory : ConfirmResidencyComponent.Factory { + override fun create( + context: AppComponentContext, + params: ConfirmResidencyComponent.Params, + ): DefaultConfirmResidencyComponent + } +} \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/component/impl/DefaultOnrampComponent.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/component/impl/DefaultOnrampComponent.kt new file mode 100644 index 0000000000..7ec0fb692a --- /dev/null +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/component/impl/DefaultOnrampComponent.kt @@ -0,0 +1,64 @@ +package com.tangem.features.onramp.component.impl + +import androidx.activity.compose.BackHandler +import androidx.compose.runtime.Composable +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.component.OnrampComponent +import com.tangem.features.onramp.component.ConfirmResidencyComponent +import com.tangem.features.onramp.entity.OnrampBottomSheetConfig +import com.tangem.features.onramp.model.OnrampModel +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject + +internal class DefaultOnrampComponent @AssistedInject constructor( + @Assisted context: AppComponentContext, + @Assisted private val params: OnrampComponent.Params, + private val confirmResidencyComponentFactory: ConfirmResidencyComponent.Factory, +) : OnrampComponent, AppComponentContext by context { + + private val model: OnrampModel = getOrCreateModel(params) + private val confirmResidencyBottomSheetSlot = childSlot( + source = model.bottomSheetNavigation, + serializer = null, + handleBackButton = false, + childFactory = ::bottomSheetChild, + ) + + @Composable + override fun Content(modifier: Modifier) { + val bottomSheet by confirmResidencyBottomSheetSlot.subscribeAsState() + BackHandler(onBack = router::pop) + + bottomSheet.child?.instance?.BottomSheet() + } + + private fun bottomSheetChild( + config: OnrampBottomSheetConfig, + componentContext: ComponentContext, + ): ComposableBottomSheetComponent = when (config) { + OnrampBottomSheetConfig.ConfirmResidency -> confirmResidencyComponentFactory.create( + context = childByContext(componentContext), + params = ConfirmResidencyComponent.Params( + countryName = "United States", + isOnrampSupported = true, + countryFlagUrl = "https://hatscripts.github.io/circle-flags/flags/us.svg", + onDismiss = model.bottomSheetNavigation::dismiss, + ), + ) + } + + @AssistedFactory + interface Factory : OnrampComponent.Factory { + override fun create(context: AppComponentContext, params: OnrampComponent.Params): DefaultOnrampComponent + } +} \ No newline at end of file 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 new file mode 100644 index 0000000000..b5e167f157 --- /dev/null +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/component/preview/PreviewConfirmResidencyComponent.kt @@ -0,0 +1,46 @@ +package com.tangem.features.onramp.component.preview + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig +import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent +import com.tangem.core.ui.extensions.stringReference +import com.tangem.features.onramp.component.ConfirmResidencyComponent +import com.tangem.features.onramp.entity.ConfirmResidencyUM +import com.tangem.features.onramp.ui.ConfirmResidencyBottomSheet +import com.tangem.features.onramp.ui.ConfirmResidencyBottomSheetContent +import kotlinx.coroutines.flow.MutableStateFlow + +internal class PreviewConfirmResidencyComponent( + initialState: ConfirmResidencyUM = ConfirmResidencyUM( + country = "United States", + countryFlagUrl = "https://hatscripts.github.io/circle-flags/flags/us.svg", + isCountrySupported = true, + primaryButtonConfig = ConfirmResidencyUM.ActionButtonConfig(onClick = {}, text = stringReference("Confirm")), + secondaryButtonConfig = ConfirmResidencyUM.ActionButtonConfig(onClick = {}, text = stringReference("Change")), + ), +) : ConfirmResidencyComponent { + + private val previewState: MutableStateFlow = MutableStateFlow(initialState) + + override fun dismiss() { + /* no-op */ + } + + @Composable + override fun BottomSheet() { + val state by previewState.collectAsStateWithLifecycle() + val bottomSheetConfig = TangemBottomSheetConfig( + isShow = true, + onDismissRequest = ::dismiss, + content = TangemBottomSheetConfigContent.Empty, + ) + ConfirmResidencyBottomSheet( + config = bottomSheetConfig, + content = { modifier -> + ConfirmResidencyBottomSheetContent(model = state, modifier = modifier) + }, + ) + } +} \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/di/OnrampComponentModule.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/di/OnrampComponentModule.kt new file mode 100644 index 0000000000..6f27d391db --- /dev/null +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/di/OnrampComponentModule.kt @@ -0,0 +1,26 @@ +package com.tangem.features.onramp.di + +import com.tangem.features.onramp.component.ConfirmResidencyComponent +import com.tangem.features.onramp.component.OnrampComponent +import com.tangem.features.onramp.component.impl.DefaultConfirmResidencyComponent +import com.tangem.features.onramp.component.impl.DefaultOnrampComponent +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 OnrampComponentModule { + + @Binds + @Singleton + fun bindOnrampComponentFactory(factory: DefaultOnrampComponent.Factory): OnrampComponent.Factory + + @Binds + @Singleton + fun bindConfirmResidencyComponentFactory( + factory: DefaultConfirmResidencyComponent.Factory, + ): ConfirmResidencyComponent.Factory +} \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/di/FeatureModule.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/di/OnrampFeatureModule.kt similarity index 94% rename from features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/di/FeatureModule.kt rename to features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/di/OnrampFeatureModule.kt index 2c39321176..cac4df29b9 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/di/FeatureModule.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/di/OnrampFeatureModule.kt @@ -11,7 +11,7 @@ import javax.inject.Singleton @Module @InstallIn(SingletonComponent::class) -internal object FeatureModule { +internal object OnrampFeatureModule { @Provides @Singleton diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/di/OnrampModelModule.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/di/OnrampModelModule.kt new file mode 100644 index 0000000000..41f90f6d5a --- /dev/null +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/di/OnrampModelModule.kt @@ -0,0 +1,26 @@ +package com.tangem.features.onramp.di + +import com.tangem.core.decompose.di.DecomposeComponent +import com.tangem.core.decompose.model.Model +import com.tangem.features.onramp.model.OnrampModel +import com.tangem.features.onramp.model.ConfirmResidencyModel +import dagger.Binds +import dagger.Module +import dagger.hilt.InstallIn +import dagger.multibindings.ClassKey +import dagger.multibindings.IntoMap + +@Module +@InstallIn(DecomposeComponent::class) +internal interface OnrampModelModule { + + @Binds + @IntoMap + @ClassKey(OnrampModel::class) + fun provideOnrampModel(model: OnrampModel): Model + + @Binds + @IntoMap + @ClassKey(ConfirmResidencyModel::class) + fun provideConfirmResidencyModel(model: ConfirmResidencyModel): Model +} \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/entity/ConfirmResidencyUM.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/entity/ConfirmResidencyUM.kt new file mode 100644 index 0000000000..106df7e1bc --- /dev/null +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/entity/ConfirmResidencyUM.kt @@ -0,0 +1,16 @@ +package com.tangem.features.onramp.entity + +import com.tangem.core.ui.extensions.TextReference + +data class ConfirmResidencyUM( + val country: String, + val countryFlagUrl: String, + val isCountrySupported: Boolean, + val primaryButtonConfig: ActionButtonConfig, + val secondaryButtonConfig: ActionButtonConfig, +) { + data class ActionButtonConfig( + val onClick: () -> Unit, + val text: TextReference, + ) +} \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/entity/OnrampBottomSheetConfig.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/entity/OnrampBottomSheetConfig.kt new file mode 100644 index 0000000000..81289b4cdf --- /dev/null +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/entity/OnrampBottomSheetConfig.kt @@ -0,0 +1,10 @@ +package com.tangem.features.onramp.entity + +import kotlinx.serialization.Serializable + +@Serializable +internal sealed class OnrampBottomSheetConfig { + + @Serializable + data object ConfirmResidency : OnrampBottomSheetConfig() +} \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/model/ConfirmResidencyModel.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/model/ConfirmResidencyModel.kt new file mode 100644 index 0000000000..9457285bd3 --- /dev/null +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/model/ConfirmResidencyModel.kt @@ -0,0 +1,53 @@ +package com.tangem.features.onramp.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.extensions.resourceReference +import com.tangem.features.onramp.component.ConfirmResidencyComponent +import com.tangem.features.onramp.entity.ConfirmResidencyUM +import com.tangem.features.onramp.impl.R +import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import kotlinx.coroutines.flow.MutableStateFlow +import javax.inject.Inject + +@ComponentScoped +internal class ConfirmResidencyModel @Inject constructor( + override val dispatchers: CoroutineDispatcherProvider, + paramsContainer: ParamsContainer, +) : Model() { + + private val params: ConfirmResidencyComponent.Params = paramsContainer.require() + val state: MutableStateFlow = MutableStateFlow( + ConfirmResidencyUM( + country = params.countryName, + countryFlagUrl = params.countryFlagUrl, + isCountrySupported = params.isOnrampSupported, + primaryButtonConfig = getPrimaryButtonConfig(), + secondaryButtonConfig = ConfirmResidencyUM.ActionButtonConfig( + onClick = ::onChangeClick, + text = resourceReference(R.string.common_change), + ), + ), + ) + + private fun getPrimaryButtonConfig() = if (params.isOnrampSupported) { + ConfirmResidencyUM.ActionButtonConfig( + onClick = params.onDismiss, + text = resourceReference(R.string.common_confirm), + ) + } else { + ConfirmResidencyUM.ActionButtonConfig( + onClick = ::onCloseClick, + text = resourceReference(R.string.common_close), + ) + } + + @Suppress("EmptyFunctionBlock") + private fun onCloseClick() { + } + + private fun onChangeClick() { + // TODO: [REDACTED_JIRA] + } +} \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/model/OnrampModel.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/model/OnrampModel.kt new file mode 100644 index 0000000000..d1f35e1fca --- /dev/null +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/model/OnrampModel.kt @@ -0,0 +1,38 @@ +package com.tangem.features.onramp.model + +import com.arkivanov.decompose.router.slot.SlotNavigation +import com.arkivanov.decompose.router.slot.activate +import com.tangem.core.decompose.di.ComponentScoped +import com.tangem.core.decompose.model.Model +import com.tangem.core.decompose.model.ParamsContainer +import com.tangem.core.decompose.navigation.Router +import com.tangem.features.onramp.component.OnrampComponent +import com.tangem.features.onramp.entity.OnrampBottomSheetConfig +import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch +import javax.inject.Inject + +@Suppress("MagicNumber") +@ComponentScoped +internal class OnrampModel @Inject constructor( + override val dispatchers: CoroutineDispatcherProvider, + private val router: Router, + paramsContainer: ParamsContainer, +) : Model() { + + @Suppress("UnusedPrivateMember") + private val params: OnrampComponent.Params = paramsContainer.require() + val bottomSheetNavigation: SlotNavigation = SlotNavigation() + + init { + modelScope.launch { + delay(1500) + bottomSheetNavigation.activate(OnrampBottomSheetConfig.ConfirmResidency) + } + } + + fun pop() { + router.pop() + } +} \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/ui/ResidenceBottomSheet.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/ui/ResidenceBottomSheet.kt new file mode 100644 index 0000000000..a950da23ab --- /dev/null +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/ui/ResidenceBottomSheet.kt @@ -0,0 +1,165 @@ +package com.tangem.features.onramp.ui + +import android.content.res.Configuration +import androidx.compose.foundation.layout.* +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.tooling.preview.PreviewParameter +import androidx.compose.ui.tooling.preview.PreviewParameterProvider +import coil.compose.SubcomposeAsyncImage +import coil.request.ImageRequest +import com.tangem.core.ui.components.CircleShimmer +import com.tangem.core.ui.components.PrimaryButton +import com.tangem.core.ui.components.SecondaryButton +import com.tangem.core.ui.components.appbar.TangemTopAppBar +import com.tangem.core.ui.components.appbar.TangemTopAppBarHeight +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.extensions.resolveReference +import com.tangem.core.ui.extensions.resourceReference +import com.tangem.core.ui.extensions.stringReference +import com.tangem.core.ui.res.TangemTheme +import com.tangem.core.ui.res.TangemThemePreview +import com.tangem.features.onramp.component.ConfirmResidencyComponent +import com.tangem.features.onramp.component.preview.PreviewConfirmResidencyComponent +import com.tangem.features.onramp.entity.ConfirmResidencyUM +import com.tangem.features.onramp.impl.R + +@Composable +internal fun ConfirmResidencyBottomSheet(config: TangemBottomSheetConfig, content: @Composable (Modifier) -> Unit) { + TangemBottomSheet( + config = config, + addBottomInsets = true, + containerColor = TangemTheme.colors.background.tertiary, + title = { _ -> Title() }, + content = { + val contentModifier = Modifier + .padding(horizontal = TangemTheme.dimens.spacing16) + .padding(bottom = TangemTheme.dimens.spacing16) + .fillMaxWidth() + .wrapContentHeight() + + content(contentModifier) + }, + ) +} + +@Composable +private fun Title(modifier: Modifier = Modifier) { + TangemTopAppBar( + modifier = modifier, + title = resourceReference(R.string.onramp_residency_bottomsheet_title), + titleAlignment = Alignment.CenterHorizontally, + height = TangemTopAppBarHeight.BOTTOM_SHEET, + ) +} + +@Composable +internal fun ConfirmResidencyBottomSheetContent(model: ConfirmResidencyUM, modifier: Modifier = Modifier) { + Column(modifier = modifier) { + CountryContent( + modifier = Modifier + .fillMaxWidth() + .padding(vertical = TangemTheme.dimens.spacing24), + name = model.country, + flagUrl = model.countryFlagUrl, + isCountrySupported = model.isCountrySupported, + ) + SecondaryButton( + modifier = Modifier.fillMaxWidth(), + text = model.secondaryButtonConfig.text.resolveReference(), + onClick = model.secondaryButtonConfig.onClick, + ) + PrimaryButton( + modifier = Modifier + .fillMaxWidth() + .padding(top = TangemTheme.dimens.spacing12), + text = model.primaryButtonConfig.text.resolveReference(), + onClick = model.primaryButtonConfig.onClick, + ) + } +} + +@Composable +private fun CountryContent(name: String, flagUrl: String, isCountrySupported: Boolean, modifier: Modifier = Modifier) { + Column( + modifier = modifier, + horizontalAlignment = Alignment.CenterHorizontally, + ) { + val flagSize = with(LocalDensity.current) { TangemTheme.dimens.size36.roundToPx() } + SubcomposeAsyncImage( + modifier = Modifier.size(TangemTheme.dimens.size36), + model = ImageRequest.Builder(LocalContext.current) + .size(size = flagSize) + .data(flagUrl) + .memoryCacheKey(flagUrl + flagSize) + .crossfade(true) + .allowHardware(false) + .build(), + loading = { CircleShimmer() }, + contentDescription = null, + ) + + Text( + modifier = Modifier.padding(top = TangemTheme.dimens.spacing12), + text = name, + color = TangemTheme.colors.text.primary1, + style = TangemTheme.typography.subtitle1, + maxLines = 1, + ) + + Text( + modifier = Modifier.padding(top = TangemTheme.dimens.spacing6), + text = stringResource( + id = if (isCountrySupported) { + R.string.onramp_residency_bottomsheet_country_subtitle + } else { + R.string.onramp_residency_bottomsheet_country_not_supported + }, + ), + color = if (isCountrySupported) TangemTheme.colors.text.tertiary else TangemTheme.colors.text.warning, + style = TangemTheme.typography.body2, + ) + } +} + +// region Preview +@Composable +@Preview(showBackground = true) +@Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES) +private fun Preview_ConfirmResidencyBottomSheet( + @PreviewParameter(ConfirmResidencyComponentPreviewProvider::class) component: ConfirmResidencyComponent, +) { + TangemThemePreview { + component.BottomSheet() + } +} + +private class ConfirmResidencyComponentPreviewProvider : PreviewParameterProvider { + override val values: Sequence + get() = sequenceOf( + PreviewConfirmResidencyComponent(), + PreviewConfirmResidencyComponent( + initialState = ConfirmResidencyUM( + country = "Russia", + countryFlagUrl = "https://hatscripts.github.io/circle-flags/flags/ru.svg", + isCountrySupported = false, + primaryButtonConfig = ConfirmResidencyUM.ActionButtonConfig( + onClick = {}, + text = stringReference("Close"), + ), + secondaryButtonConfig = ConfirmResidencyUM.ActionButtonConfig( + onClick = {}, + text = stringReference("Change"), + ), + ), + ), + ) +} \ No newline at end of file