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 914a9ad067..ce6d8603f5 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 @@ -40,7 +40,6 @@ sealed class AppRoute(val path: String) : Route { val isTosAccepted: Boolean, ) : AppRoute(path = "/disclaimer${if (isTosAccepted) "/tos_accepted" else ""}") - @Deprecated("Do not use! Should be replaced by an implementation in onboarding component") @Serializable data object OnboardingNote : AppRoute(path = "/onboarding/note") diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/common/ui/WalletCard.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/common/ui/WalletCard.kt new file mode 100644 index 0000000000..79f9406a97 --- /dev/null +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/common/ui/WalletCard.kt @@ -0,0 +1,26 @@ +package com.tangem.features.onboarding.v2.common.ui + +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.painterResource +import coil.compose.AsyncImage +import coil.request.ImageRequest +import com.tangem.features.onboarding.v2.impl.R + +@Composable +internal fun WalletCard(url: String?, modifier: Modifier = Modifier) { + AsyncImage( + modifier = modifier, + model = ImageRequest.Builder(LocalContext.current) + .data(url) + .crossfade(true) + .build(), + placeholder = painterResource(R.drawable.card_placeholder_black), + error = painterResource(R.drawable.card_placeholder_black), + fallback = painterResource(R.drawable.card_placeholder_black), + contentScale = ContentScale.Fit, + contentDescription = null, + ) +} \ No newline at end of file diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/ui/WalletArtwork.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/ui/WalletArtwork.kt index 40eac45b30..cb4b29a905 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/ui/WalletArtwork.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/ui/WalletArtwork.kt @@ -15,19 +15,14 @@ import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.graphicsLayer -import androidx.compose.ui.layout.ContentScale -import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalDensity -import androidx.compose.ui.res.painterResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.util.fastForEach import androidx.compose.ui.zIndex -import coil.compose.AsyncImage -import coil.request.ImageRequest import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemThemePreview -import com.tangem.features.onboarding.v2.impl.R +import com.tangem.features.onboarding.v2.common.ui.WalletCard import kotlinx.coroutines.delay import java.util.concurrent.TimeUnit @@ -244,22 +239,6 @@ private fun AnimatedWalletCards( } } -@Composable -private fun WalletCard(url: String?, modifier: Modifier = Modifier) { - AsyncImage( - modifier = modifier, - model = ImageRequest.Builder(LocalContext.current) - .data(url) - .crossfade(true) - .build(), - placeholder = painterResource(R.drawable.card_placeholder_black), - error = painterResource(R.drawable.card_placeholder_black), - fallback = painterResource(R.drawable.card_placeholder_black), - contentScale = ContentScale.Fit, - contentDescription = null, - ) -} - private fun WalletArtworksState.toTransitionSetState(maxWidthDp: Float, maxHeightDp: Float, density: Float) = when (this) { WalletArtworksState.Hidden -> listOf() diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/note/api/OnboardingNoteComponent.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/note/api/OnboardingNoteComponent.kt index 3bbab7074a..bbf7743446 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/note/api/OnboardingNoteComponent.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/note/api/OnboardingNoteComponent.kt @@ -1,11 +1,12 @@ package com.tangem.features.onboarding.v2.note.api import com.tangem.core.decompose.factory.ComponentFactory +import com.tangem.core.decompose.navigation.inner.InnerNavigationHolder import com.tangem.core.ui.decompose.ComposableContentComponent import com.tangem.domain.models.scan.ScanResponse import com.tangem.features.onboarding.v2.TitleProvider -interface OnboardingNoteComponent : ComposableContentComponent { +interface OnboardingNoteComponent : ComposableContentComponent, InnerNavigationHolder { data class Params( val titleProvider: TitleProvider, diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/note/impl/DefaultOnboardingNoteComponent.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/note/impl/DefaultOnboardingNoteComponent.kt index 3dacc2bdc0..d3ca96fa30 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/note/impl/DefaultOnboardingNoteComponent.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/note/impl/DefaultOnboardingNoteComponent.kt @@ -1,21 +1,128 @@ package com.tangem.features.onboarding.v2.note.impl import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier +import com.arkivanov.decompose.extensions.compose.stack.Children +import com.arkivanov.decompose.extensions.compose.stack.animation.slide +import com.arkivanov.decompose.extensions.compose.stack.animation.stackAnimation +import com.arkivanov.decompose.extensions.compose.subscribeAsState +import com.arkivanov.decompose.router.stack.ChildStack +import com.arkivanov.decompose.router.stack.childStack +import com.arkivanov.decompose.router.stack.push +import com.arkivanov.decompose.value.Value +import com.arkivanov.decompose.value.subscribe +import com.arkivanov.essenty.instancekeeper.getOrCreateSimple 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.decompose.navigation.inner.InnerNavigation +import com.tangem.core.decompose.navigation.inner.InnerNavigationState +import com.tangem.core.ui.decompose.ComposableContentComponent +import com.tangem.core.ui.extensions.TextReference +import com.tangem.features.onboarding.v2.impl.R import com.tangem.features.onboarding.v2.note.api.OnboardingNoteComponent +import com.tangem.features.onboarding.v2.note.impl.child.create.OnboardingNoteCreateWalletComponent +import com.tangem.features.onboarding.v2.note.impl.child.topup.OnboardingNoteTopUpComponent +import com.tangem.features.onboarding.v2.note.impl.model.OnboardingNoteModel +import com.tangem.features.onboarding.v2.note.impl.route.ONBOARDING_NOTE_STEPS_COUNT +import com.tangem.features.onboarding.v2.note.impl.route.OnboardingNoteRoute import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.SharedFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.launch internal class DefaultOnboardingNoteComponent @AssistedInject constructor( @Assisted context: AppComponentContext, @Assisted val params: OnboardingNoteComponent.Params, ) : OnboardingNoteComponent, AppComponentContext by context { + private val model: OnboardingNoteModel = getOrCreateModel(params) + + private val currentChildBackEventHandle = instanceKeeper.getOrCreateSimple(key = "currentChildBackEventHandle") { + MutableSharedFlow() + } + + private val childParams = ChildParams( + onBack = ::onChildBack, + parentBackEvent = currentChildBackEventHandle, + ) + + private val childStack: Value> = + childStack( + key = "innerStack", + source = model.stackNavigation, + serializer = null, + initialConfiguration = model.initialRoute, + handleBackButton = true, + childFactory = { configuration, factoryContext -> + createChild( + configuration, + childByContext(factoryContext), + ) + }, + ) + + override val innerNavigation: InnerNavigation = object : InnerNavigation { + override val state: StateFlow = model.innerNavigationState + + override fun pop(onComplete: (Boolean) -> Unit) { + // TODO [REDACTED_TASK_KEY] + } + } + + init { + componentScope.launch { + model.onDone.collect { params.onDone() } + } + + // sets title and stepper value + childStack.subscribe(lifecycle) { stack -> + val currentRoute = stack.active.configuration + params.titleProvider.changeTitle(TextReference.Res(R.string.onboarding_title)) + model.updateStepForNewRoute(currentRoute) + } + } + + private fun createChild( + route: OnboardingNoteRoute, + factoryContext: AppComponentContext, + ): ComposableContentComponent { + return when (route) { + OnboardingNoteRoute.CreateWallet -> OnboardingNoteCreateWalletComponent( + appComponentContext = factoryContext, + params = OnboardingNoteCreateWalletComponent.Params( + childParams = childParams, + onDone = { model.stackNavigation.push(OnboardingNoteRoute.TopUp) }, + ), + ) + OnboardingNoteRoute.TopUp -> OnboardingNoteTopUpComponent( + appComponentContext = factoryContext, + params = OnboardingNoteTopUpComponent.Params( + childParams = childParams, + onDone = { model.onDone.tryEmit(Unit) }, + ), + ) + } + } + + private fun onChildBack() = model.onChildBack( + currentRoute = childStack.value.active.configuration, + ) + @Composable override fun Content(modifier: Modifier) { - // TODO [REDACTED_TASK_KEY] + val stackState by childStack.subscribeAsState() + + Children( + stack = stackState, + animation = stackAnimation(slide()), + ) { + it.instance.Content(modifier) + } } @AssistedFactory @@ -25,4 +132,17 @@ internal class DefaultOnboardingNoteComponent @AssistedInject constructor( params: OnboardingNoteComponent.Params, ): DefaultOnboardingNoteComponent } -} \ No newline at end of file + + data class ChildParams( + val onBack: () -> Unit, + val parentBackEvent: SharedFlow, + ) +} + +internal data class OnboardingNoteInnerNavigationState( + override val stackSize: Int, +) : InnerNavigationState { + override val stackMaxSize: Int = ONBOARDING_NOTE_STEPS_COUNT +} + +internal const val ALL_STEPS_TOP_CONTAINER_WEIGHT = 0.48f \ No newline at end of file diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/note/impl/child/create/OnboardingNoteCreateWalletComponent.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/note/impl/child/create/OnboardingNoteCreateWalletComponent.kt new file mode 100644 index 0000000000..5751eb80ba --- /dev/null +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/note/impl/child/create/OnboardingNoteCreateWalletComponent.kt @@ -0,0 +1,39 @@ +package com.tangem.features.onboarding.v2.note.impl.child.create + +import androidx.activity.compose.BackHandler +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.decompose.ComposableContentComponent +import com.tangem.features.onboarding.v2.note.impl.DefaultOnboardingNoteComponent +import com.tangem.features.onboarding.v2.note.impl.child.create.model.OnboardingNoteCreateWalletModel +import com.tangem.features.onboarding.v2.note.impl.child.create.ui.OnboardingNoteCreateWallet + +internal class OnboardingNoteCreateWalletComponent( + appComponentContext: AppComponentContext, + private val params: Params, +) : ComposableContentComponent, AppComponentContext by appComponentContext { + + private val model: OnboardingNoteCreateWalletModel = getOrCreateModel(params) + + @Composable + override fun Content(modifier: Modifier) { + val state by model.uiState.collectAsStateWithLifecycle() + + BackHandler(onBack = remember(this) { { params.childParams.onBack() } }) + + OnboardingNoteCreateWallet( + modifier = modifier, + state = state, + ) + } + + data class Params( + val childParams: DefaultOnboardingNoteComponent.ChildParams, + val onDone: () -> Unit, + ) +} \ No newline at end of file diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/note/impl/child/create/model/OnboardingNoteCreateWalletModel.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/note/impl/child/create/model/OnboardingNoteCreateWalletModel.kt new file mode 100644 index 0000000000..93634beb79 --- /dev/null +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/note/impl/child/create/model/OnboardingNoteCreateWalletModel.kt @@ -0,0 +1,26 @@ +package com.tangem.features.onboarding.v2.note.impl.child.create.model + +import com.tangem.core.decompose.di.ModelScoped +import com.tangem.core.decompose.model.Model +import com.tangem.core.decompose.model.ParamsContainer +import com.tangem.features.onboarding.v2.note.impl.child.create.ui.state.OnboardingNoteCreateWalletUM +import com.tangem.sdk.api.TangemSdkManager +import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import javax.inject.Inject + +@Suppress("UnusedPrivateMember") +@ModelScoped +internal class OnboardingNoteCreateWalletModel @Inject constructor( + paramsContainer: ParamsContainer, + override val dispatchers: CoroutineDispatcherProvider, + private val tangemSdkManager: TangemSdkManager, +) : Model() { + + private val _uiState = MutableStateFlow( + OnboardingNoteCreateWalletUM(), + ) + + val uiState: StateFlow = _uiState +} \ No newline at end of file diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/note/impl/child/create/ui/OnboardingNoteCreateWallet.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/note/impl/child/create/ui/OnboardingNoteCreateWallet.kt new file mode 100644 index 0000000000..eba5be11b9 --- /dev/null +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/note/impl/child/create/ui/OnboardingNoteCreateWallet.kt @@ -0,0 +1,92 @@ +package com.tangem.features.onboarding.v2.note.impl.child.create.ui + +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.tangem.core.ui.components.PrimaryButtonIconEnd +import com.tangem.core.ui.extensions.stringResourceSafe +import com.tangem.core.ui.res.TangemTheme +import com.tangem.core.ui.res.TangemThemePreview +import com.tangem.features.onboarding.v2.impl.R +import com.tangem.features.onboarding.v2.multiwallet.impl.ui.WalletArtworks +import com.tangem.features.onboarding.v2.multiwallet.impl.ui.WalletArtworksState +import com.tangem.features.onboarding.v2.note.impl.ALL_STEPS_TOP_CONTAINER_WEIGHT +import com.tangem.features.onboarding.v2.note.impl.child.create.ui.state.OnboardingNoteCreateWalletUM + +@Composable +internal fun OnboardingNoteCreateWallet(state: OnboardingNoteCreateWalletUM, modifier: Modifier = Modifier) { + Column( + modifier = modifier + .fillMaxSize() + .navigationBarsPadding(), + verticalArrangement = Arrangement.Bottom, + ) { + WalletArtworks( + url1 = state.artworkUrl, + url2 = null, + url3 = null, + modifier = Modifier + .padding(horizontal = 34.dp) + .padding(top = 56.dp) + .weight(ALL_STEPS_TOP_CONTAINER_WEIGHT) + .fillMaxWidth(), + state = WalletArtworksState.Folded, + ) + Column( + Modifier + .fillMaxWidth() + .verticalScroll(rememberScrollState()) + .padding(32.dp) + .weight(1.0f), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + Text( + text = stringResource(R.string.onboarding_create_wallet_header), + style = TangemTheme.typography.h2, + color = TangemTheme.colors.text.primary1, + textAlign = TextAlign.Center, + modifier = Modifier.padding(top = 16.dp), + ) + + Text( + text = stringResource(R.string.onboarding_create_wallet_body), + style = TangemTheme.typography.body1, + color = TangemTheme.colors.text.secondary, + textAlign = TextAlign.Center, + modifier = Modifier.padding(top = 12.dp), + ) + } + + PrimaryButtonIconEnd( + modifier = Modifier + .padding(start = 16.dp, end = 16.dp, bottom = 16.dp) + .fillMaxWidth(), + iconResId = R.drawable.ic_tangem_24, + text = stringResourceSafe(R.string.onboarding_create_wallet_button_create_wallet), + onClick = state.onCreateClick, + showProgress = state.createWalletInProgress, + ) + } +} + +@Preview(showBackground = true) +@Composable +private fun OnboardingNoteCreatePreview() { + TangemThemePreview { + OnboardingNoteCreateWallet( + state = OnboardingNoteCreateWalletUM( + artworkUrl = null, + createWalletInProgress = false, + onCreateClick = {}, + ), + ) + } +} \ No newline at end of file diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/note/impl/child/create/ui/state/OnboardingNoteCreateWalletUM.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/note/impl/child/create/ui/state/OnboardingNoteCreateWalletUM.kt new file mode 100644 index 0000000000..950445d00d --- /dev/null +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/note/impl/child/create/ui/state/OnboardingNoteCreateWalletUM.kt @@ -0,0 +1,7 @@ +package com.tangem.features.onboarding.v2.note.impl.child.create.ui.state + +data class OnboardingNoteCreateWalletUM( + val artworkUrl: String? = null, + val createWalletInProgress: Boolean = false, + val onCreateClick: () -> Unit = {}, +) \ No newline at end of file diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/note/impl/child/topup/OnboardingNoteTopUpComponent.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/note/impl/child/topup/OnboardingNoteTopUpComponent.kt new file mode 100644 index 0000000000..5b9cc4f1af --- /dev/null +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/note/impl/child/topup/OnboardingNoteTopUpComponent.kt @@ -0,0 +1,39 @@ +package com.tangem.features.onboarding.v2.note.impl.child.topup + +import androidx.activity.compose.BackHandler +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.decompose.ComposableContentComponent +import com.tangem.features.onboarding.v2.note.impl.DefaultOnboardingNoteComponent +import com.tangem.features.onboarding.v2.note.impl.child.topup.model.OnboardingNoteTopUpModel +import com.tangem.features.onboarding.v2.note.impl.child.topup.ui.OnboardingNoteTopUp + +internal class OnboardingNoteTopUpComponent( + appComponentContext: AppComponentContext, + private val params: Params, +) : ComposableContentComponent, AppComponentContext by appComponentContext { + + private val model: OnboardingNoteTopUpModel = getOrCreateModel(params) + + @Composable + override fun Content(modifier: Modifier) { + val state by model.uiState.collectAsStateWithLifecycle() + + BackHandler(onBack = remember(this) { { params.childParams.onBack() } }) + + OnboardingNoteTopUp( + modifier = modifier, + state = state, + ) + } + + data class Params( + val childParams: DefaultOnboardingNoteComponent.ChildParams, + val onDone: () -> Unit, + ) +} \ No newline at end of file diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/note/impl/child/topup/model/OnboardingNoteTopUpModel.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/note/impl/child/topup/model/OnboardingNoteTopUpModel.kt new file mode 100644 index 0000000000..f2a8e3bf60 --- /dev/null +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/note/impl/child/topup/model/OnboardingNoteTopUpModel.kt @@ -0,0 +1,24 @@ +package com.tangem.features.onboarding.v2.note.impl.child.topup.model + +import com.tangem.core.decompose.di.ModelScoped +import com.tangem.core.decompose.model.Model +import com.tangem.core.decompose.model.ParamsContainer +import com.tangem.features.onboarding.v2.note.impl.child.topup.ui.state.OnboardingNoteTopUpUM +import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import javax.inject.Inject + +@Suppress("UnusedPrivateMember") +@ModelScoped +internal class OnboardingNoteTopUpModel @Inject constructor( + paramsContainer: ParamsContainer, + override val dispatchers: CoroutineDispatcherProvider, +) : Model() { + + private val _uiState = MutableStateFlow( + OnboardingNoteTopUpUM(), + ) + + val uiState: StateFlow = _uiState +} \ No newline at end of file diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/note/impl/child/topup/ui/OnboardinNoteTopUpHeader.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/note/impl/child/topup/ui/OnboardinNoteTopUpHeader.kt new file mode 100644 index 0000000000..e0c96f997b --- /dev/null +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/note/impl/child/topup/ui/OnboardinNoteTopUpHeader.kt @@ -0,0 +1,125 @@ +package com.tangem.features.onboarding.v2.note.impl.child.topup.ui + +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.Icon +import androidx.compose.material3.Text +import androidx.compose.material3.ripple +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.shadow +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.tangem.core.ui.components.SpacerH8 +import com.tangem.core.ui.components.SpacerHMax +import com.tangem.core.ui.extensions.stringResourceSafe +import com.tangem.core.ui.res.TangemTheme +import com.tangem.core.ui.res.TangemThemePreview +import com.tangem.features.onboarding.v2.common.ui.WalletCard +import com.tangem.features.onboarding.v2.impl.R + +@Composable +fun OnboardinNoteTopUpHeader( + balance: String?, + cardArtworkUrl: String?, + isRefreshing: Boolean, + onRefreshBalanceClick: () -> Unit, + modifier: Modifier = Modifier, +) { + Box( + modifier = modifier.fillMaxWidth(), + contentAlignment = Alignment.Center, + ) { + Box( + modifier = Modifier + .padding(vertical = 24.dp, horizontal = 32.dp) + .fillMaxSize() + .background( + TangemTheme.colors.button.secondary, + shape = TangemTheme.shapes.roundedCornersMedium, + ), + ) + Column( + horizontalAlignment = Alignment.CenterHorizontally, + modifier = Modifier.padding(horizontal = 32.dp), + ) { + WalletCard( + modifier = Modifier.width(120.dp), + url = cardArtworkUrl, + ) + SpacerHMax() + Text( + text = stringResourceSafe(R.string.common_balance_title), + style = TangemTheme.typography.body2, + color = TangemTheme.colors.text.secondary, + textAlign = TextAlign.Center, + ) + SpacerH8() + Text( + text = balance.orEmpty(), + style = TangemTheme.typography.h2, + color = TangemTheme.colors.text.primary1, + textAlign = TextAlign.Center, + ) + SpacerHMax() + RefreshButton( + isRefreshing = isRefreshing, + onRefreshBalanceClick = onRefreshBalanceClick, + ) + } + } +} + +@Composable +private fun RefreshButton(isRefreshing: Boolean, onRefreshBalanceClick: () -> Unit, modifier: Modifier = Modifier) { + Box( + modifier = modifier + .size(TangemTheme.dimens.size48) + .shadow(elevation = 2.dp, shape = CircleShape) + .background(TangemTheme.colors.background.action) + .clickable( + enabled = !isRefreshing, + onClick = onRefreshBalanceClick, + indication = ripple(), + interactionSource = remember { MutableInteractionSource() }, + ), + ) { + if (isRefreshing) { + Box { + CircularProgressIndicator( + modifier = Modifier.padding(TangemTheme.dimens.spacing12), + color = TangemTheme.colors.icon.primary1, + strokeWidth = TangemTheme.dimens.size2, + ) + } + } else { + Icon( + painter = painterResource(id = R.drawable.ic_refresh_24), + contentDescription = null, + tint = TangemTheme.colors.text.disabled, + modifier = Modifier.padding(TangemTheme.dimens.spacing12), + ) + } + } +} + +@Preview(showBackground = true) +@Composable +private fun OnboardinNoteTopUpHeaderPreview() { + TangemThemePreview { + OnboardinNoteTopUpHeader( + balance = "0.00000001 BTC", + cardArtworkUrl = "", + onRefreshBalanceClick = {}, + isRefreshing = false, + ) + } +} \ No newline at end of file diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/note/impl/child/topup/ui/OnboardingNoteTopUp.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/note/impl/child/topup/ui/OnboardingNoteTopUp.kt new file mode 100644 index 0000000000..a850b14829 --- /dev/null +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/note/impl/child/topup/ui/OnboardingNoteTopUp.kt @@ -0,0 +1,112 @@ +package com.tangem.features.onboarding.v2.note.impl.child.topup.ui + +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.tangem.core.ui.components.PrimaryButtonIconEnd +import com.tangem.core.ui.components.SecondaryButton +import com.tangem.core.ui.components.SpacerH8 +import com.tangem.core.ui.extensions.stringResourceSafe +import com.tangem.core.ui.res.TangemTheme +import com.tangem.core.ui.res.TangemThemePreview +import com.tangem.features.onboarding.v2.impl.R +import com.tangem.features.onboarding.v2.note.impl.ALL_STEPS_TOP_CONTAINER_WEIGHT +import com.tangem.features.onboarding.v2.note.impl.child.topup.ui.state.OnboardingNoteTopUpUM + +@Composable +fun OnboardingNoteTopUp(state: OnboardingNoteTopUpUM, modifier: Modifier = Modifier) { + Column( + modifier = modifier + .fillMaxSize() + .navigationBarsPadding(), + verticalArrangement = Arrangement.Bottom, + ) { + OnboardinNoteTopUpHeader( + balance = state.balance, + cardArtworkUrl = state.cardArtworkUrl, + onRefreshBalanceClick = state.onRefreshBalanceClick, + isRefreshing = state.isRefreshing, + modifier = Modifier + .padding(horizontal = 34.dp) + .padding(top = 20.dp) + .weight(ALL_STEPS_TOP_CONTAINER_WEIGHT) + .fillMaxWidth(), + ) + Column( + modifier = Modifier + .fillMaxWidth() + .verticalScroll(rememberScrollState()) + .padding(32.dp) + .weight(1.0f), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + Text( + text = stringResourceSafe(R.string.onboarding_topup_title), + style = TangemTheme.typography.h2, + color = TangemTheme.colors.text.primary1, + textAlign = TextAlign.Center, + modifier = Modifier.padding(top = 16.dp), + ) + + Text( + text = stringResourceSafe(R.string.onboarding_top_up_body), + style = TangemTheme.typography.body1, + color = TangemTheme.colors.text.secondary, + textAlign = TextAlign.Center, + modifier = Modifier.padding(top = 12.dp), + ) + } + + BottomButtons(state) + } +} + +@Composable +private fun BottomButtons(state: OnboardingNoteTopUpUM) { + if (state.availableForBuy) { + PrimaryButtonIconEnd( + modifier = Modifier + .padding(horizontal = 16.dp) + .fillMaxWidth(), + iconResId = R.drawable.ic_tangem_24, + text = stringResourceSafe(R.string.onboarding_top_up_button_but_crypto), + onClick = state.onBuyCryptoClick, + ) + SpacerH8() + SecondaryButton( + modifier = Modifier + .padding(start = 16.dp, end = 16.dp, bottom = 16.dp) + .fillMaxWidth(), + text = stringResourceSafe(R.string.onboarding_top_up_button_show_wallet_address), + onClick = state.onShowWalletAddressClick, + ) + } else { + PrimaryButtonIconEnd( + modifier = Modifier + .padding(start = 16.dp, end = 16.dp, bottom = 16.dp) + .fillMaxWidth(), + iconResId = R.drawable.ic_tangem_24, + text = stringResourceSafe(R.string.onboarding_button_receive_crypto), + onClick = state.onShowWalletAddressClick, + ) + } +} + +@Preview(showBackground = true) +@Composable +private fun OnboardingNoteTopUpPreview() { + TangemThemePreview { + OnboardingNoteTopUp( + state = OnboardingNoteTopUpUM( + availableForBuy = true, + ), + ) + } +} \ No newline at end of file diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/note/impl/child/topup/ui/state/OnboardingNoteTopUpUM.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/note/impl/child/topup/ui/state/OnboardingNoteTopUpUM.kt new file mode 100644 index 0000000000..762ccae2e8 --- /dev/null +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/note/impl/child/topup/ui/state/OnboardingNoteTopUpUM.kt @@ -0,0 +1,12 @@ +package com.tangem.features.onboarding.v2.note.impl.child.topup.ui.state + +data class OnboardingNoteTopUpUM( + val cardArtworkUrl: String? = null, + val availableForBuy: Boolean = true, + val balance: String? = null, + val isRefreshing: Boolean = false, + val amountToCreateAccount: String? = null, + val onBuyCryptoClick: () -> Unit = {}, + val onShowWalletAddressClick: () -> Unit = {}, + val onRefreshBalanceClick: () -> Unit = {}, +) \ No newline at end of file diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/note/impl/di/ComponentModule.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/note/impl/di/ComponentModule.kt index 49d9a5b659..be064600cc 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/note/impl/di/ComponentModule.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/note/impl/di/ComponentModule.kt @@ -1,11 +1,18 @@ package com.tangem.features.onboarding.v2.note.impl.di +import com.tangem.core.decompose.di.ModelComponent +import com.tangem.core.decompose.model.Model import com.tangem.features.onboarding.v2.note.api.OnboardingNoteComponent import com.tangem.features.onboarding.v2.note.impl.DefaultOnboardingNoteComponent +import com.tangem.features.onboarding.v2.note.impl.child.create.model.OnboardingNoteCreateWalletModel +import com.tangem.features.onboarding.v2.note.impl.child.topup.model.OnboardingNoteTopUpModel +import com.tangem.features.onboarding.v2.note.impl.model.OnboardingNoteModel import dagger.Binds import dagger.Module import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent +import dagger.multibindings.ClassKey +import dagger.multibindings.IntoMap import javax.inject.Singleton @Module @@ -15,4 +22,24 @@ internal interface ComponentModule { @Binds @Singleton fun bindComponent(factory: DefaultOnboardingNoteComponent.Factory): OnboardingNoteComponent.Factory +} + +@Module +@InstallIn(ModelComponent::class) +internal interface ModelModule { + + @Binds + @IntoMap + @ClassKey(OnboardingNoteModel::class) + fun provideNoteModel(model: OnboardingNoteModel): Model + + @Binds + @IntoMap + @ClassKey(OnboardingNoteCreateWalletModel::class) + fun provideNoteCreateWalletModel(model: OnboardingNoteCreateWalletModel): Model + + @Binds + @IntoMap + @ClassKey(OnboardingNoteTopUpModel::class) + fun provideNoteTopUpModel(model: OnboardingNoteTopUpModel): Model } \ No newline at end of file diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/note/impl/model/OnboardingNoteModel.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/note/impl/model/OnboardingNoteModel.kt new file mode 100644 index 0000000000..87a00a91e1 --- /dev/null +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/note/impl/model/OnboardingNoteModel.kt @@ -0,0 +1,58 @@ +package com.tangem.features.onboarding.v2.note.impl.model + +import com.arkivanov.decompose.router.stack.StackNavigation +import com.tangem.core.decompose.di.ModelScoped +import com.tangem.core.decompose.model.Model +import com.tangem.core.decompose.model.ParamsContainer +import com.tangem.features.onboarding.v2.note.api.OnboardingNoteComponent +import com.tangem.features.onboarding.v2.note.impl.OnboardingNoteInnerNavigationState +import com.tangem.features.onboarding.v2.note.impl.route.OnboardingNoteRoute +import com.tangem.features.onboarding.v2.note.impl.route.stepNum +import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import javax.inject.Inject + +@ModelScoped +internal class OnboardingNoteModel @Inject constructor( + paramsContainer: ParamsContainer, + override val dispatchers: CoroutineDispatcherProvider, +) : Model() { + + @Suppress("UnusedPrivateMember") + private val params = paramsContainer.require() + + private val _currentScanResponse = MutableStateFlow(params.scanResponse) + + val initialRoute = OnboardingNoteRoute.CreateWallet // TODO [REDACTED_TASK_KEY] + + private val _innerNavigationState = + MutableStateFlow(OnboardingNoteInnerNavigationState(stackSize = initialRoute.stepNum())) + + val currentScanResponse = _currentScanResponse.asStateFlow() + + val innerNavigationState = _innerNavigationState.asStateFlow() + + val onDone = MutableSharedFlow() + + val stackNavigation = StackNavigation() + + private val _uiState = MutableStateFlow( + OnboardingNoteState(), + ) + + val uiState: StateFlow = _uiState + + fun updateStepForNewRoute(route: OnboardingNoteRoute) { + _innerNavigationState.value = innerNavigationState.value.copy( + stackSize = route.stepNum(), + ) + } + + @Suppress("UnusedPrivateMember") + fun onChildBack(currentRoute: OnboardingNoteRoute) { + // TODO [REDACTED_TASK_KEY] + } +} \ No newline at end of file diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/note/impl/model/OnboardingNoteState.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/note/impl/model/OnboardingNoteState.kt new file mode 100644 index 0000000000..7d183852bb --- /dev/null +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/note/impl/model/OnboardingNoteState.kt @@ -0,0 +1,5 @@ +package com.tangem.features.onboarding.v2.note.impl.model + +data class OnboardingNoteState( + val cardArtworkUrl: String? = null, +) \ No newline at end of file diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/note/impl/route/OnboardingNoteRoute.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/note/impl/route/OnboardingNoteRoute.kt new file mode 100644 index 0000000000..1d7573e677 --- /dev/null +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/note/impl/route/OnboardingNoteRoute.kt @@ -0,0 +1,15 @@ +package com.tangem.features.onboarding.v2.note.impl.route + +import kotlinx.serialization.Serializable + +@Serializable +internal sealed class OnboardingNoteRoute { + + @Serializable + data object CreateWallet : OnboardingNoteRoute() + + @Serializable + data object TopUp : OnboardingNoteRoute() +} + +internal const val ONBOARDING_NOTE_STEPS_COUNT = 3 \ No newline at end of file diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/note/impl/route/Step.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/note/impl/route/Step.kt new file mode 100644 index 0000000000..3df643e22b --- /dev/null +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/note/impl/route/Step.kt @@ -0,0 +1,6 @@ +package com.tangem.features.onboarding.v2.note.impl.route + +internal fun OnboardingNoteRoute.stepNum() = when (this) { + OnboardingNoteRoute.CreateWallet -> 1 + OnboardingNoteRoute.TopUp -> 2 +} \ No newline at end of file