Updated on 2026-08-14
This commit is contained in:
parent
0fe5355c74
commit
c32507b283
21 changed files with 344 additions and 108 deletions
|
|
@ -1,61 +1,62 @@
|
|||
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.stack.Children
|
||||
import com.arkivanov.decompose.extensions.compose.jetpack.stack.animation.stackAnimation
|
||||
import com.arkivanov.decompose.extensions.compose.jetpack.subscribeAsState
|
||||
import com.arkivanov.decompose.router.slot.childSlot
|
||||
import com.arkivanov.decompose.router.slot.dismiss
|
||||
import com.arkivanov.decompose.router.stack.StackNavigation
|
||||
import com.arkivanov.decompose.router.stack.childStack
|
||||
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.core.ui.decompose.ComposableContentComponent
|
||||
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 com.tangem.features.onramp.entity.OnrampChild
|
||||
import com.tangem.features.onramp.settings.OnrampSettingsComponent
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
|
||||
@Suppress("UnusedPrivateMember")
|
||||
internal class DefaultOnrampComponent @AssistedInject constructor(
|
||||
@Assisted context: AppComponentContext,
|
||||
@Assisted private val params: OnrampComponent.Params,
|
||||
private val confirmResidencyComponentFactory: ConfirmResidencyComponent.Factory,
|
||||
@Assisted params: OnrampComponent.Params,
|
||||
private val settingsComponentFactory: OnrampSettingsComponent.Factory,
|
||||
) : OnrampComponent, AppComponentContext by context {
|
||||
|
||||
private val model: OnrampModel = getOrCreateModel(params)
|
||||
private val confirmResidencyBottomSheetSlot = childSlot(
|
||||
source = model.bottomSheetNavigation,
|
||||
serializer = null,
|
||||
private val navigation = StackNavigation<OnrampChild>()
|
||||
private val contentStack = childStack(
|
||||
key = "onramp_content_stack",
|
||||
source = navigation,
|
||||
serializer = OnrampChild.serializer(),
|
||||
initialConfiguration = OnrampChild.Settings,
|
||||
handleBackButton = false,
|
||||
childFactory = ::bottomSheetChild,
|
||||
childFactory = ::screenChild,
|
||||
)
|
||||
|
||||
@Composable
|
||||
override fun Content(modifier: Modifier) {
|
||||
val bottomSheet by confirmResidencyBottomSheetSlot.subscribeAsState()
|
||||
BackHandler(onBack = router::pop)
|
||||
val childStack by contentStack.subscribeAsState()
|
||||
|
||||
bottomSheet.child?.instance?.BottomSheet()
|
||||
Children(
|
||||
stack = childStack,
|
||||
animation = stackAnimation(),
|
||||
) { child ->
|
||||
child.instance.Content(modifier = modifier)
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
),
|
||||
)
|
||||
}
|
||||
private fun screenChild(config: OnrampChild, componentContext: ComponentContext): ComposableContentComponent =
|
||||
when (config) {
|
||||
OnrampChild.Settings -> settingsComponentFactory.create(
|
||||
context = childByContext(componentContext),
|
||||
// params = OnrampSettingsComponent.Params(navigation::pop) // todo uncomment after [REDACTED_JIRA]
|
||||
params = OnrampSettingsComponent.Params(router::pop),
|
||||
)
|
||||
OnrampChild.Main -> TODO("[REDACTED_JIRA]")
|
||||
}
|
||||
|
||||
@AssistedFactory
|
||||
interface Factory : OnrampComponent.Factory {
|
||||
|
|
|
|||
|
|
@ -1,26 +0,0 @@
|
|||
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
|
||||
}
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
package com.tangem.features.onramp.entity
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
internal sealed class OnrampBottomSheetConfig {
|
||||
|
||||
@Serializable
|
||||
data object ConfirmResidency : OnrampBottomSheetConfig()
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
package com.tangem.features.onramp.entity
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
internal sealed class OnrampChild {
|
||||
@Serializable
|
||||
data object Main : OnrampChild()
|
||||
|
||||
@Serializable
|
||||
data object Settings : OnrampChild()
|
||||
}
|
||||
|
|
@ -1,38 +0,0 @@
|
|||
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<OnrampBottomSheetConfig> = SlotNavigation()
|
||||
|
||||
init {
|
||||
modelScope.launch {
|
||||
delay(1500)
|
||||
bottomSheetNavigation.activate(OnrampBottomSheetConfig.ConfirmResidency)
|
||||
}
|
||||
}
|
||||
|
||||
fun pop() {
|
||||
router.pop()
|
||||
}
|
||||
}
|
||||
|
|
@ -3,7 +3,7 @@ package com.tangem.features.onramp.selectcountry
|
|||
import com.tangem.core.decompose.factory.ComponentFactory
|
||||
import com.tangem.core.ui.decompose.ComposableBottomSheetComponent
|
||||
|
||||
interface SelectCountryComponent : ComposableBottomSheetComponent {
|
||||
internal interface SelectCountryComponent : ComposableBottomSheetComponent {
|
||||
|
||||
data class Params(val onDismiss: () -> Unit)
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,63 @@
|
|||
package com.tangem.features.onramp.settings
|
||||
|
||||
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.selectcountry.SelectCountryComponent
|
||||
import com.tangem.features.onramp.settings.entity.OnrampSettingsConfig
|
||||
import com.tangem.features.onramp.settings.model.OnrampSettingsModel
|
||||
import com.tangem.features.onramp.settings.ui.OnrampSettingsContent
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
|
||||
internal class DefaultOnrampSettingsComponent @AssistedInject constructor(
|
||||
@Assisted appComponentContext: AppComponentContext,
|
||||
@Assisted private val params: OnrampSettingsComponent.Params,
|
||||
private val selectCountryComponentFactory: SelectCountryComponent.Factory,
|
||||
) : OnrampSettingsComponent, AppComponentContext by appComponentContext {
|
||||
|
||||
private val model: OnrampSettingsModel = getOrCreateModel(params)
|
||||
private val bottomSheetSlot = childSlot(
|
||||
source = model.bottomSheetNavigation,
|
||||
serializer = null,
|
||||
handleBackButton = false,
|
||||
childFactory = ::bottomSheetChild,
|
||||
)
|
||||
|
||||
@Composable
|
||||
override fun Content(modifier: Modifier) {
|
||||
BackHandler(onBack = params.onBack)
|
||||
OnrampSettingsContent(modifier = modifier, state = model.state)
|
||||
|
||||
val bottomSheet by bottomSheetSlot.subscribeAsState()
|
||||
bottomSheet.child?.instance?.BottomSheet()
|
||||
}
|
||||
|
||||
private fun bottomSheetChild(
|
||||
config: OnrampSettingsConfig,
|
||||
componentContext: ComponentContext,
|
||||
): ComposableBottomSheetComponent = when (config) {
|
||||
OnrampSettingsConfig.SelectCountry -> selectCountryComponentFactory.create(
|
||||
context = childByContext(componentContext),
|
||||
params = SelectCountryComponent.Params(model.bottomSheetNavigation::dismiss),
|
||||
)
|
||||
}
|
||||
|
||||
@AssistedFactory
|
||||
interface Factory : OnrampSettingsComponent.Factory {
|
||||
override fun create(
|
||||
context: AppComponentContext,
|
||||
params: OnrampSettingsComponent.Params,
|
||||
): DefaultOnrampSettingsComponent
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
package com.tangem.features.onramp.settings
|
||||
|
||||
import com.tangem.core.decompose.factory.ComponentFactory
|
||||
import com.tangem.core.ui.decompose.ComposableContentComponent
|
||||
|
||||
internal interface OnrampSettingsComponent : ComposableContentComponent {
|
||||
|
||||
data class Params(val onBack: () -> Unit)
|
||||
|
||||
interface Factory : ComponentFactory<Params, OnrampSettingsComponent>
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
package com.tangem.features.onramp.settings.di
|
||||
|
||||
import com.tangem.features.onramp.settings.DefaultOnrampSettingsComponent
|
||||
import com.tangem.features.onramp.settings.OnrampSettingsComponent
|
||||
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 OnrampSettingsComponentModule {
|
||||
|
||||
@Binds
|
||||
@Singleton
|
||||
fun bindOnrampSettingsComponentFactory(
|
||||
factory: DefaultOnrampSettingsComponent.Factory,
|
||||
): OnrampSettingsComponent.Factory
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
package com.tangem.features.onramp.settings.di
|
||||
|
||||
import com.tangem.core.decompose.di.DecomposeComponent
|
||||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.features.onramp.settings.model.OnrampSettingsModel
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.multibindings.ClassKey
|
||||
import dagger.multibindings.IntoMap
|
||||
|
||||
@Module
|
||||
@InstallIn(DecomposeComponent::class)
|
||||
internal interface OnrampSettingsModelModule {
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@ClassKey(OnrampSettingsModel::class)
|
||||
fun bindOnrampSettingsModelModel(model: OnrampSettingsModel): Model
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
package com.tangem.features.onramp.settings.entity
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
sealed class OnrampSettingsConfig {
|
||||
|
||||
data object SelectCountry : OnrampSettingsConfig()
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
package com.tangem.features.onramp.settings.entity
|
||||
|
||||
sealed class OnrampSettingsItemUM {
|
||||
data class Residence(val countryName: String, val flagUrl: String, val onClick: () -> Unit) : OnrampSettingsItemUM()
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
package com.tangem.features.onramp.settings.entity
|
||||
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
||||
internal data class OnrampSettingsUM(
|
||||
val onBack: () -> Unit,
|
||||
val items: ImmutableList<OnrampSettingsItemUM>,
|
||||
)
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
package com.tangem.features.onramp.settings.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.features.onramp.settings.OnrampSettingsComponent
|
||||
import com.tangem.features.onramp.settings.entity.OnrampSettingsConfig
|
||||
import com.tangem.features.onramp.settings.entity.OnrampSettingsItemUM
|
||||
import com.tangem.features.onramp.settings.entity.OnrampSettingsUM
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import javax.inject.Inject
|
||||
|
||||
@ComponentScoped
|
||||
internal class OnrampSettingsModel @Inject constructor(
|
||||
override val dispatchers: CoroutineDispatcherProvider,
|
||||
paramsContainer: ParamsContainer,
|
||||
) : Model() {
|
||||
|
||||
private val params: OnrampSettingsComponent.Params = paramsContainer.require()
|
||||
val bottomSheetNavigation: SlotNavigation<OnrampSettingsConfig> = SlotNavigation()
|
||||
val state = OnrampSettingsUM(
|
||||
onBack = params.onBack,
|
||||
items = listOf(
|
||||
OnrampSettingsItemUM.Residence(
|
||||
countryName = "United States of America",
|
||||
flagUrl = "",
|
||||
onClick = { bottomSheetNavigation.activate(OnrampSettingsConfig.SelectCountry) },
|
||||
),
|
||||
).toImmutableList(),
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,111 @@
|
|||
package com.tangem.features.onramp.settings.ui
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.key
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.util.fastForEach
|
||||
import coil.compose.AsyncImage
|
||||
import com.tangem.core.ui.components.SpacerWMax
|
||||
import com.tangem.core.ui.components.appbar.AppBarWithBackButton
|
||||
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.settings.entity.OnrampSettingsItemUM
|
||||
import com.tangem.features.onramp.settings.entity.OnrampSettingsUM
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
||||
@Composable
|
||||
internal fun OnrampSettingsContent(state: OnrampSettingsUM, modifier: Modifier = Modifier) {
|
||||
Scaffold(
|
||||
modifier = modifier,
|
||||
contentWindowInsets = WindowInsetsZero,
|
||||
containerColor = TangemTheme.colors.background.secondary,
|
||||
topBar = {
|
||||
AppBarWithBackButton(
|
||||
modifier = Modifier.statusBarsPadding(),
|
||||
onBackClick = state.onBack,
|
||||
text = stringResource(id = R.string.onramp_settings_title),
|
||||
iconRes = R.drawable.ic_close_24,
|
||||
)
|
||||
},
|
||||
content = { innerPadding ->
|
||||
Content(
|
||||
modifier = Modifier
|
||||
.padding(innerPadding)
|
||||
.fillMaxSize()
|
||||
.padding(horizontal = TangemTheme.dimens.spacing16),
|
||||
items = state.items,
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun Content(items: ImmutableList<OnrampSettingsItemUM>, modifier: Modifier = Modifier) {
|
||||
Column(modifier = modifier) {
|
||||
items.fastForEach { model ->
|
||||
key(model) {
|
||||
when (model) {
|
||||
is OnrampSettingsItemUM.Residence -> ResidenceSection(model)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ResidenceSection(state: OnrampSettingsItemUM.Residence, modifier: Modifier = Modifier) {
|
||||
Column(
|
||||
modifier = modifier,
|
||||
verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing8),
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.clip(RoundedCornerShape(TangemTheme.dimens.radius16))
|
||||
.background(TangemTheme.colors.background.primary)
|
||||
.clickable(onClick = state.onClick)
|
||||
.padding(TangemTheme.dimens.spacing12)
|
||||
.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing8),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(id = R.string.onramp_settings_residence),
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
style = TangemTheme.typography.subtitle2,
|
||||
)
|
||||
SpacerWMax()
|
||||
AsyncImage(
|
||||
modifier = Modifier.size(TangemTheme.dimens.size20),
|
||||
model = state.flagUrl,
|
||||
contentDescription = null,
|
||||
)
|
||||
Text(
|
||||
text = state.countryName,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
style = TangemTheme.typography.body2,
|
||||
)
|
||||
Icon(
|
||||
painter = painterResource(id = R.drawable.ic_chevron_right_24),
|
||||
contentDescription = null,
|
||||
tint = TangemTheme.colors.icon.informative,
|
||||
)
|
||||
}
|
||||
Text(
|
||||
text = stringResource(id = R.string.onramp_settings_residence_description),
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
style = TangemTheme.typography.caption2,
|
||||
)
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue