Updated on 2026-08-14
This commit is contained in:
parent
faafa2724a
commit
04b7287332
45 changed files with 1597 additions and 96 deletions
|
|
@ -14,6 +14,7 @@ import com.tangem.domain.models.scan.CardDTO
|
|||
import com.tangem.domain.models.scan.ProductType
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.settings.usercountry.models.UserCountry
|
||||
import com.tangem.domain.visa.model.VisaCardActivationStatus
|
||||
import com.tangem.domain.wallets.builder.UserWalletBuilder
|
||||
import com.tangem.domain.wallets.builder.UserWalletIdBuilder
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
|
|
@ -44,6 +45,12 @@ object OnboardingHelper {
|
|||
store.state.globalState.onboardingState.onboardingManager ?: OnboardingManager(response)
|
||||
val cardId = response.card.cardId
|
||||
return when {
|
||||
response.cardTypesResolver.isVisaWallet() -> {
|
||||
if (response.visaCardActivationStatus == null) error("Visa card activation status is null")
|
||||
|
||||
response.visaCardActivationStatus !is VisaCardActivationStatus.Activated
|
||||
}
|
||||
|
||||
response.cardTypesResolver.isTangemTwins() -> {
|
||||
if (!response.twinsIsTwinned()) {
|
||||
true
|
||||
|
|
@ -68,7 +75,8 @@ object OnboardingHelper {
|
|||
fun whereToNavigate(scanResponse: ScanResponse): AppRoute {
|
||||
val newOnboardingSupportTypes = scanResponse.productType == ProductType.Wallet2 ||
|
||||
scanResponse.productType == ProductType.Ring ||
|
||||
scanResponse.productType == ProductType.Wallet // AppRoute.OnboardingOther is also supported
|
||||
scanResponse.productType == ProductType.Wallet ||
|
||||
scanResponse.productType == ProductType.Visa // AppRoute.OnboardingOther is also supported
|
||||
if (store.inject(DaggerGraphState::onboardingV2FeatureToggles).isOnboardingV2Enabled &&
|
||||
newOnboardingSupportTypes
|
||||
) {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,42 @@
|
|||
package com.tangem.core.ui.extensions
|
||||
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.drawWithCache
|
||||
import androidx.compose.ui.geometry.CornerRadius
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
import androidx.compose.ui.geometry.isSimple
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.Outline
|
||||
import androidx.compose.ui.graphics.Shape
|
||||
import androidx.compose.ui.graphics.drawscope.Stroke
|
||||
import androidx.compose.ui.unit.Dp
|
||||
|
||||
/**
|
||||
* Modifier that adds an outset border to the content.
|
||||
* @see [androidx.compose.foundation.border]
|
||||
*
|
||||
* @param width The width of the border.
|
||||
* @param color The color of the border.
|
||||
* @param shape The shape of the border.
|
||||
*/
|
||||
fun Modifier.outsetBorder(width: Dp, color: Color, shape: Shape) = drawWithCache {
|
||||
val outline = shape.createOutline(size, layoutDirection, this)
|
||||
val cornerRadius = when {
|
||||
outline is Outline.Rounded && outline.roundRect.isSimple -> outline.roundRect.topLeftCornerRadius
|
||||
else -> CornerRadius.Zero
|
||||
}
|
||||
|
||||
onDrawWithContent {
|
||||
drawContent()
|
||||
drawRoundRect(
|
||||
color = color,
|
||||
topLeft = Offset(-width.toPx() / 2, -width.toPx() / 2),
|
||||
size = size.copy(
|
||||
width = size.width + width.toPx(),
|
||||
height = size.height + width.toPx(),
|
||||
),
|
||||
cornerRadius = cornerRadius,
|
||||
style = Stroke(width = width.toPx()),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
package com.tangem.core.ui.security
|
||||
|
||||
import android.view.WindowManager
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.DisposableEffect
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import com.tangem.core.ui.utils.findActivity
|
||||
|
||||
/**
|
||||
* Disables screenshots for the current composition.
|
||||
*/
|
||||
@Composable
|
||||
fun DisableScreenshotsDisposableEffect() {
|
||||
val activity = LocalContext.current.findActivity()
|
||||
DisposableEffect(activity) {
|
||||
activity.window.setFlags(
|
||||
WindowManager.LayoutParams.FLAG_SECURE,
|
||||
WindowManager.LayoutParams.FLAG_SECURE,
|
||||
)
|
||||
onDispose {
|
||||
activity.window.clearFlags(WindowManager.LayoutParams.FLAG_SECURE)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
package com.tangem.core.ui.utils
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.content.ContextWrapper
|
||||
|
||||
fun Context.findActivity(): Activity {
|
||||
var context = this
|
||||
while (context is ContextWrapper) {
|
||||
if (context is Activity) return context
|
||||
context = context.baseContext
|
||||
}
|
||||
error("Cannot find activity. Please make sure that the context is attached to an activity.")
|
||||
}
|
||||
9
core/ui/src/main/res/drawable/ic_loader.xml
Normal file
9
core/ui/src/main/res/drawable/ic_loader.xml
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:autoMirrored="true" android:height="8dp" android:viewportHeight="8" android:viewportWidth="32" android:width="32dp">
|
||||
|
||||
<path android:fillColor="#0099FF" android:pathData="M28,8C25.791,8 24,6.209 24,4C24,1.791 25.791,0 28,0C30.209,0 32,1.791 32,4C32,6.209 30.209,8 28,8Z"/>
|
||||
|
||||
<path android:fillAlpha="0.55" android:fillColor="#0099FF" android:pathData="M15,7C13.343,7 12,5.657 12,4C12,2.343 13.343,1 15,1C16.657,1 18,2.343 18,4C18,5.657 16.657,7 15,7Z" android:strokeAlpha="0.55"/>
|
||||
|
||||
<path android:fillAlpha="0.25" android:fillColor="#0099FF" android:pathData="M3,7C1.343,7 0,5.657 0,4C0,2.343 1.343,1 3,1C4.657,1 6,2.343 6,4C6,5.657 4.657,7 3,7Z" android:strokeAlpha="0.25"/>
|
||||
|
||||
</vector>
|
||||
18
core/ui/src/main/res/drawable/ic_tangem_card_grey.xml
Normal file
18
core/ui/src/main/res/drawable/ic_tangem_card_grey.xml
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="315dp"
|
||||
android:height="198dp"
|
||||
android:viewportWidth="315"
|
||||
android:viewportHeight="198">
|
||||
<path
|
||||
android:pathData="M10,0L305,0A10,10 0,0 1,315 10L315,188A10,10 0,0 1,305 198L10,198A10,10 0,0 1,0 188L0,10A10,10 0,0 1,10 0z"
|
||||
android:fillColor="#C9C9C9"/>
|
||||
<path
|
||||
android:pathData="M167.85,73.95H147.56C144.35,73.95 142.75,73.95 141.53,74.57C140.45,75.13 139.58,76.01 139.03,77.09C138.41,78.33 138.41,79.94 138.41,83.17V86.66H177V83.17C177,79.94 177,78.33 176.38,77.09C175.83,76.01 174.95,75.13 173.88,74.57C172.66,73.95 171.05,73.95 167.85,73.95Z"
|
||||
android:fillColor="#4A4A4A"/>
|
||||
<path
|
||||
android:pathData="M164.13,97.13H177V113.35C177,116.57 177,118.18 176.37,119.42C175.83,120.5 174.95,121.38 173.88,121.94C172.65,122.56 171.05,122.56 167.85,122.56H164.13V97.13Z"
|
||||
android:fillColor="#4A4A4A"/>
|
||||
<path
|
||||
android:pathData="M151.27,97.13H138.41V113.35C138.41,116.57 138.41,118.18 139.03,119.42C139.58,120.5 140.45,121.38 141.53,121.94C142.75,122.56 144.35,122.56 147.55,122.56H151.27V97.13Z"
|
||||
android:fillColor="#4A4A4A"/>
|
||||
</vector>
|
||||
7
core/ui/src/main/res/drawable/ic_wallet_filled_24.xml
Normal file
7
core/ui/src/main/res/drawable/ic_wallet_filled_24.xml
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:autoMirrored="true" android:height="24dp" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
|
||||
|
||||
<path android:fillColor="#1E1E1E" android:fillType="evenOdd" android:pathData="M5,6.25C3.481,6.25 2.25,7.481 2.25,9V18C2.25,19.519 3.481,20.75 5,20.75H19C20.519,20.75 21.75,19.519 21.75,18V9C21.75,7.481 20.519,6.25 19,6.25H5ZM16.5,12.25C15.81,12.25 15.25,12.81 15.25,13.5C15.25,14.19 15.81,14.75 16.5,14.75C17.19,14.75 17.75,14.19 17.75,13.5C17.75,12.81 17.19,12.25 16.5,12.25Z"/>
|
||||
|
||||
<path android:fillColor="#1E1E1E" android:pathData="M16.485,3.069C17.754,2.73 19,3.687 19,5.001H9L16.485,3.069Z"/>
|
||||
|
||||
</vector>
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 70 KiB After Width: | Height: | Size: 786 KiB |
|
|
@ -72,6 +72,7 @@ internal class OnboardingEntryModel @Inject constructor(
|
|||
ProductType.Visa -> OnboardingRoute.Visa(
|
||||
scanResponse = scanResponse,
|
||||
titleProvider = titleProvider,
|
||||
onDone = ::onVisaOnboardingDone,
|
||||
)
|
||||
else -> error("Unsupported")
|
||||
}
|
||||
|
|
@ -89,6 +90,12 @@ internal class OnboardingEntryModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun onVisaOnboardingDone() {
|
||||
stackNavigation.navigate {
|
||||
listOf(OnboardingRoute.Done(onDone = ::navigateToWalletScreen))
|
||||
}
|
||||
}
|
||||
|
||||
private fun navigateToWalletScreen() {
|
||||
modelScope.launch(NonCancellable) {
|
||||
router.replaceAll(AppRoute.Wallet)
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ internal class OnboardingChildFactory @Inject constructor(
|
|||
params = OnboardingVisaComponent.Params(
|
||||
scanResponse = route.scanResponse,
|
||||
titleProvider = route.titleProvider,
|
||||
onDone = route.onDone,
|
||||
),
|
||||
)
|
||||
is OnboardingRoute.ManageTokens -> onboardingManageTokensComponentFactory.create(
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ sealed class OnboardingRoute : Route {
|
|||
data class Visa(
|
||||
val titleProvider: TitleProvider,
|
||||
val scanResponse: ScanResponse,
|
||||
val onDone: () -> Unit,
|
||||
) : OnboardingRoute()
|
||||
|
||||
data class ManageTokens(
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ internal class DefaultOnboardingMultiWalletComponent @AssistedInject constructor
|
|||
|
||||
private val model: OnboardingMultiWalletModel = getOrCreateModel(params)
|
||||
private val stackNavigation = StackNavigation<OnboardingMultiWalletState.Step>()
|
||||
private val artworksState = instanceKeeper.getOrCreateSimple {
|
||||
private val artworksState = instanceKeeper.getOrCreateSimple(key = "artworksState") {
|
||||
MutableStateFlow(
|
||||
when (model.state.value.currentStep) {
|
||||
CreateWallet -> WalletArtworksState.Folded
|
||||
|
|
@ -68,9 +68,11 @@ internal class DefaultOnboardingMultiWalletComponent @AssistedInject constructor
|
|||
)
|
||||
}
|
||||
|
||||
private val innerNavigationStateFlow = MutableStateFlow(
|
||||
MultiWalletInnerNavigationState(1, 5),
|
||||
)
|
||||
private val innerNavigationStateFlow = instanceKeeper.getOrCreateSimple(key = "innerNavigationState") {
|
||||
MutableStateFlow(
|
||||
MultiWalletInnerNavigationState(1, 5),
|
||||
)
|
||||
}
|
||||
|
||||
private val childParams = MultiWalletChildParams(
|
||||
multiWalletState = model.state,
|
||||
|
|
|
|||
|
|
@ -1,19 +1,14 @@
|
|||
package com.tangem.features.onboarding.v2.multiwallet.impl.child.seedphrase
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.content.ContextWrapper
|
||||
import android.view.WindowManager
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.DisposableEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.tangem.core.decompose.context.AppComponentContext
|
||||
import com.tangem.core.decompose.model.getOrCreateModel
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.security.DisableScreenshotsDisposableEffect
|
||||
import com.tangem.features.onboarding.v2.impl.R
|
||||
import com.tangem.features.onboarding.v2.multiwallet.impl.child.MultiWalletChildComponent
|
||||
import com.tangem.features.onboarding.v2.multiwallet.impl.child.MultiWalletChildParams
|
||||
|
|
@ -79,30 +74,11 @@ internal class MultiWalletSeedPhraseComponent(
|
|||
model.onBack()
|
||||
}
|
||||
|
||||
// disable screenshots
|
||||
val activity = LocalContext.current.findActivity()
|
||||
DisposableEffect(activity) {
|
||||
activity.window.setFlags(
|
||||
WindowManager.LayoutParams.FLAG_SECURE,
|
||||
WindowManager.LayoutParams.FLAG_SECURE,
|
||||
)
|
||||
onDispose {
|
||||
activity.window.clearFlags(WindowManager.LayoutParams.FLAG_SECURE)
|
||||
}
|
||||
}
|
||||
DisableScreenshotsDisposableEffect()
|
||||
|
||||
MultiWalletSeedPhrase(
|
||||
modifier = modifier,
|
||||
state = state,
|
||||
)
|
||||
}
|
||||
|
||||
private fun Context.findActivity(): Activity {
|
||||
var context = this
|
||||
while (context is ContextWrapper) {
|
||||
if (context is Activity) return context
|
||||
context = context.baseContext
|
||||
}
|
||||
error("")
|
||||
}
|
||||
}
|
||||
|
|
@ -11,6 +11,7 @@ interface OnboardingVisaComponent : ComposableContentComponent, InnerNavigationH
|
|||
data class Params(
|
||||
val scanResponse: ScanResponse,
|
||||
val titleProvider: TitleProvider,
|
||||
val onDone: () -> Unit,
|
||||
)
|
||||
|
||||
interface Factory : ComponentFactory<Params, OnboardingVisaComponent>
|
||||
|
|
|
|||
|
|
@ -1,15 +1,34 @@
|
|||
package com.tangem.features.onboarding.v2.visa.impl
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import com.arkivanov.decompose.extensions.compose.jetpack.stack.Children
|
||||
import com.arkivanov.decompose.extensions.compose.jetpack.stack.animation.slide
|
||||
import com.arkivanov.decompose.extensions.compose.jetpack.stack.animation.stackAnimation
|
||||
import com.arkivanov.decompose.extensions.compose.jetpack.subscribeAsState
|
||||
import com.arkivanov.decompose.router.stack.*
|
||||
import com.arkivanov.decompose.value.Value
|
||||
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.features.onboarding.v2.visa.api.OnboardingVisaComponent
|
||||
import com.tangem.features.onboarding.v2.visa.impl.child.accesscode.OnboardingVisaAccessCodeComponent
|
||||
import com.tangem.features.onboarding.v2.visa.impl.child.approve.OnboardingVisaApproveComponent
|
||||
import com.tangem.features.onboarding.v2.visa.impl.child.choosewallet.OnboardingVisaChooseWalletComponent
|
||||
import com.tangem.features.onboarding.v2.visa.impl.child.inprogress.OnboardingVisaInProgressComponent
|
||||
import com.tangem.features.onboarding.v2.visa.impl.child.otherwallet.OnboardingVisaOtherWalletComponent
|
||||
import com.tangem.features.onboarding.v2.visa.impl.child.pincode.OnboardingVisaPinCodeComponent
|
||||
import com.tangem.features.onboarding.v2.visa.impl.child.welcome.OnboardingVisaWelcomeComponent
|
||||
import com.tangem.features.onboarding.v2.visa.impl.model.OnboardingVisaModel
|
||||
import com.tangem.features.onboarding.v2.visa.impl.route.OnboardingVisaRoute
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
import kotlinx.coroutines.channels.BufferOverflow
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
|
|
@ -21,27 +40,144 @@ internal class DefaultOnboardingVisaComponent @AssistedInject constructor(
|
|||
@Assisted private val params: OnboardingVisaComponent.Params,
|
||||
) : OnboardingVisaComponent, AppComponentContext by appComponentContext {
|
||||
|
||||
private val innerNavigationState = MutableStateFlow(
|
||||
OnboardingVisaInnerNavigationState(
|
||||
stackSize = 1,
|
||||
stackMaxSize = 7,
|
||||
),
|
||||
)
|
||||
private val model: OnboardingVisaModel = getOrCreateModel(params)
|
||||
|
||||
private val currentChildBackEventHandle = MutableSharedFlow<Unit>(onBufferOverflow = BufferOverflow.DROP_LATEST)
|
||||
private val innerNavigationState = instanceKeeper.getOrCreateSimple(key = "innerNavigationState") {
|
||||
MutableStateFlow(
|
||||
OnboardingVisaInnerNavigationState(
|
||||
stackSize = 1,
|
||||
stackMaxSize = 7,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private val currentChildBackEventHandle = instanceKeeper.getOrCreateSimple(key = "currentChildBackEventHandle") {
|
||||
MutableSharedFlow<Unit>()
|
||||
}
|
||||
|
||||
private val stackNavigation = StackNavigation<OnboardingVisaRoute>()
|
||||
|
||||
private val childStack: Value<ChildStack<OnboardingVisaRoute, ComposableContentComponent>> =
|
||||
childStack(
|
||||
key = "innerStack",
|
||||
source = 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<InnerNavigationState> = innerNavigationState
|
||||
|
||||
override fun pop(onComplete: (Boolean) -> Unit) {
|
||||
// TODO add stack handling
|
||||
componentScope.launch { currentChildBackEventHandle.emit(Unit) }
|
||||
if (childStack.value.active.configuration is OnboardingVisaRoute.AccessCode) {
|
||||
componentScope.launch { currentChildBackEventHandle.emit(Unit) }
|
||||
} else {
|
||||
stackNavigation.pop()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun createChild(
|
||||
route: OnboardingVisaRoute,
|
||||
factoryContext: AppComponentContext,
|
||||
): ComposableContentComponent {
|
||||
return when (route) {
|
||||
is OnboardingVisaRoute.Welcome -> OnboardingVisaWelcomeComponent(
|
||||
appComponentContext = factoryContext,
|
||||
params = OnboardingVisaWelcomeComponent.Params(
|
||||
isWelcomeBack = route.isWelcomeBack,
|
||||
onBack = ::onChildBack,
|
||||
onDone = { stackNavigation.push(OnboardingVisaRoute.AccessCode) },
|
||||
),
|
||||
)
|
||||
OnboardingVisaRoute.AccessCode -> OnboardingVisaAccessCodeComponent(
|
||||
appComponentContext = factoryContext,
|
||||
params = OnboardingVisaAccessCodeComponent.Params(
|
||||
onBack = ::onChildBack,
|
||||
parentBackEvent = currentChildBackEventHandle,
|
||||
onDone = { stackNavigation.push(OnboardingVisaRoute.ChooseWallet) },
|
||||
),
|
||||
)
|
||||
OnboardingVisaRoute.ChooseWallet -> OnboardingVisaChooseWalletComponent(
|
||||
appComponentContext = factoryContext,
|
||||
params = OnboardingVisaChooseWalletComponent.Params(
|
||||
onBack = ::onChildBack,
|
||||
onEvent = { event ->
|
||||
stackNavigation.push(
|
||||
when (event) {
|
||||
OnboardingVisaChooseWalletComponent.Params.Event.TangemWallet ->
|
||||
OnboardingVisaRoute.TangemWalletApproveOption
|
||||
OnboardingVisaChooseWalletComponent.Params.Event.OtherWallet ->
|
||||
OnboardingVisaRoute.OtherWalletApproveOption
|
||||
},
|
||||
)
|
||||
},
|
||||
),
|
||||
)
|
||||
OnboardingVisaRoute.InProgress -> OnboardingVisaInProgressComponent(
|
||||
appComponentContext = factoryContext,
|
||||
params = OnboardingVisaInProgressComponent.Params(
|
||||
onBack = ::onChildBack,
|
||||
onDone = { stackNavigation.push(OnboardingVisaRoute.PinCode) },
|
||||
),
|
||||
)
|
||||
OnboardingVisaRoute.OtherWalletApproveOption -> OnboardingVisaOtherWalletComponent(
|
||||
appComponentContext = factoryContext,
|
||||
params = OnboardingVisaOtherWalletComponent.Params(
|
||||
onBack = ::onChildBack,
|
||||
onDone = { stackNavigation.push(OnboardingVisaRoute.InProgress) },
|
||||
),
|
||||
)
|
||||
OnboardingVisaRoute.PinCode -> OnboardingVisaPinCodeComponent(
|
||||
appComponentContext = factoryContext,
|
||||
params = OnboardingVisaPinCodeComponent.Params(
|
||||
onBack = ::onChildBack,
|
||||
onDone = { params.onDone() },
|
||||
),
|
||||
)
|
||||
OnboardingVisaRoute.TangemWalletApproveOption -> OnboardingVisaApproveComponent(
|
||||
appComponentContext = factoryContext,
|
||||
params = OnboardingVisaApproveComponent.Params(
|
||||
onBack = ::onChildBack,
|
||||
onDone = { stackNavigation.push(OnboardingVisaRoute.InProgress) },
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun onChildBack() {
|
||||
when (childStack.value.active.configuration) {
|
||||
OnboardingVisaRoute.AccessCode,
|
||||
OnboardingVisaRoute.TangemWalletApproveOption,
|
||||
OnboardingVisaRoute.OtherWalletApproveOption,
|
||||
-> stackNavigation.pop()
|
||||
is OnboardingVisaRoute.Welcome,
|
||||
OnboardingVisaRoute.ChooseWallet,
|
||||
OnboardingVisaRoute.InProgress,
|
||||
OnboardingVisaRoute.OtherWalletApproveOption,
|
||||
OnboardingVisaRoute.PinCode,
|
||||
-> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
override fun Content(modifier: Modifier) {
|
||||
TODO("Not yet implemented")
|
||||
val stackState by childStack.subscribeAsState()
|
||||
|
||||
Children(
|
||||
stack = stackState,
|
||||
animation = stackAnimation(slide()),
|
||||
) {
|
||||
it.instance.Content(modifier)
|
||||
}
|
||||
}
|
||||
|
||||
@AssistedFactory
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ 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.core.ui.security.DisableScreenshotsDisposableEffect
|
||||
import com.tangem.features.onboarding.v2.visa.impl.child.accesscode.model.OnboardingVisaAccessCodeModel
|
||||
import com.tangem.features.onboarding.v2.visa.impl.child.accesscode.ui.OnboardingVisaAccessCode
|
||||
import kotlinx.coroutines.flow.SharedFlow
|
||||
|
|
@ -29,6 +30,9 @@ internal class OnboardingVisaAccessCodeComponent(
|
|||
model.onBack()
|
||||
}
|
||||
}
|
||||
componentScope.launch {
|
||||
model.onDone.collect { params.onDone() }
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
|
|
@ -37,11 +41,14 @@ internal class OnboardingVisaAccessCodeComponent(
|
|||
|
||||
BackHandler(onBack = model::onBack)
|
||||
|
||||
DisableScreenshotsDisposableEffect()
|
||||
|
||||
OnboardingVisaAccessCode(state, modifier)
|
||||
}
|
||||
|
||||
data class Params(
|
||||
val parentBackEvent: SharedFlow<Unit>,
|
||||
val onBack: () -> Unit,
|
||||
val onDone: () -> Unit,
|
||||
)
|
||||
}
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
package com.tangem.features.onboarding.v2.visa.impl.child.accesscode.model
|
||||
|
||||
import androidx.compose.runtime.Stable
|
||||
import androidx.compose.ui.text.input.TextFieldValue
|
||||
import com.tangem.core.decompose.di.ComponentScoped
|
||||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.features.onboarding.v2.visa.impl.child.accesscode.ui.state.OnboardingVisaAccessCodeUM
|
||||
|
|
@ -14,12 +15,14 @@ import javax.inject.Inject
|
|||
@ComponentScoped
|
||||
internal class OnboardingVisaAccessCodeModel @Inject constructor(
|
||||
override val dispatchers: CoroutineDispatcherProvider,
|
||||
@Suppress("UnusedPrivateMember")
|
||||
private val tangemSdkManager: TangemSdkManager,
|
||||
) : Model() {
|
||||
|
||||
private val _uiState = MutableStateFlow(getInitialState())
|
||||
val uiState = _uiState.asStateFlow()
|
||||
val onBack = MutableSharedFlow<Unit>()
|
||||
val onDone = MutableSharedFlow<Unit>()
|
||||
|
||||
fun onBack() {
|
||||
when (uiState.value.step) {
|
||||
|
|
@ -31,64 +34,90 @@ internal class OnboardingVisaAccessCodeModel @Inject constructor(
|
|||
|
||||
private fun getInitialState(): OnboardingVisaAccessCodeUM {
|
||||
return OnboardingVisaAccessCodeUM(
|
||||
onAccessCodeFirstChange = { _uiState.update { it.copy(accessCodeFirst = it.accessCodeFirst) } },
|
||||
onAccessCodeSecondChange = { _uiState.update { it.copy(accessCodeSecond = it.accessCodeSecond) } },
|
||||
onAccessCodeFirstChange = ::onAccessCodeFirstChange,
|
||||
onAccessCodeSecondChange = ::onAccessCodeSecondChange,
|
||||
onAccessCodeHideClick = { _uiState.update { it.copy(accessCodeHidden = !it.accessCodeHidden) } },
|
||||
onContinue = { onContinue() },
|
||||
)
|
||||
}
|
||||
|
||||
private fun onContinue() {
|
||||
if (checkAccessCode().not()) return
|
||||
private fun onAccessCodeFirstChange(textFieldValue: TextFieldValue) {
|
||||
_uiState.update {
|
||||
it.copy(
|
||||
accessCodeFirst = textFieldValue,
|
||||
atLeastMinCharsError = false,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun onAccessCodeSecondChange(textFieldValue: TextFieldValue) {
|
||||
_uiState.update {
|
||||
it.copy(
|
||||
accessCodeSecond = textFieldValue,
|
||||
codesNotMatchError = false,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun onContinue() {
|
||||
when (uiState.value.step) {
|
||||
OnboardingVisaAccessCodeUM.Step.Enter -> {
|
||||
if (checkAccessCodeMinChars().not()) return
|
||||
_uiState.update { it.copy(step = OnboardingVisaAccessCodeUM.Step.ReEnter) }
|
||||
}
|
||||
OnboardingVisaAccessCodeUM.Step.ReEnter -> {
|
||||
if (checkAccessCodesMatch().not()) return
|
||||
startActivationProcess(accessCode = uiState.value.accessCodeFirst.text)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkAccessCode(): Boolean {
|
||||
private fun checkAccessCodeMinChars(): Boolean {
|
||||
val first = uiState.value.accessCodeFirst.text
|
||||
val second = uiState.value.accessCodeSecond.text
|
||||
|
||||
when {
|
||||
first.length < ACCESS_CODE_MIN_LENGTH -> {
|
||||
_uiState.update { it.copy(atLeastMinCharsError = true) }
|
||||
return false
|
||||
}
|
||||
first != second -> {
|
||||
_uiState.update { it.copy(codesNotMatchError = true) }
|
||||
return false
|
||||
}
|
||||
if (first.length < ACCESS_CODE_MIN_LENGTH) {
|
||||
_uiState.update { it.copy(atLeastMinCharsError = true) }
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
private fun startActivationProcess(accessCode: String) {
|
||||
modelScope.launch {
|
||||
@Suppress("UnusedPrivateMember")
|
||||
val result = tangemSdkManager.activateVisaCard(
|
||||
accessCode = accessCode,
|
||||
challengeToSign = null,
|
||||
activationInput = TODO(),
|
||||
)
|
||||
private fun checkAccessCodesMatch(): Boolean {
|
||||
val first = uiState.value.accessCodeFirst.text
|
||||
val second = uiState.value.accessCodeSecond.text
|
||||
|
||||
// when (result) {
|
||||
// is CompletionResult.Success -> {
|
||||
// val response = result.data
|
||||
// TODO()
|
||||
// }
|
||||
// is CompletionResult.Failure -> {
|
||||
// // show alert
|
||||
// TODO()
|
||||
// }
|
||||
// }
|
||||
if (first != second) {
|
||||
_uiState.update { it.copy(codesNotMatchError = true) }
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
@Suppress("UnusedPrivateMember")
|
||||
private fun startActivationProcess(accessCode: String) {
|
||||
modelScope.launch { onDone.emit(Unit) }
|
||||
|
||||
// modelScope.launch {
|
||||
// @Suppress("UnusedPrivateMember")
|
||||
// val result = tangemSdkManager.activateVisaCard(
|
||||
// accessCode = accessCode,
|
||||
// challengeToSign = null,
|
||||
// activationInput = TODO(),
|
||||
// )
|
||||
//
|
||||
// when (result) {
|
||||
// is CompletionResult.Success -> {
|
||||
// val response = result.data
|
||||
// TODO()
|
||||
// }
|
||||
// is CompletionResult.Failure -> {
|
||||
// // show alert
|
||||
// TODO()
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
private companion object {
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ internal fun OnboardingVisaAccessCode(state: OnboardingVisaAccessCodeUM, modifie
|
|||
.weight(1f),
|
||||
targetState = state.step,
|
||||
transitionSpec = TangemAnimations.AnimatedContent
|
||||
.slide { initial, target -> initial.ordinal < target.ordinal },
|
||||
.slide { initial, target -> initial.ordinal > target.ordinal },
|
||||
) { step ->
|
||||
Content(
|
||||
state = state,
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ package com.tangem.features.onboarding.v2.visa.impl.child.accesscode.ui.state
|
|||
|
||||
import androidx.compose.ui.text.input.TextFieldValue
|
||||
|
||||
data class OnboardingVisaAccessCodeUM(
|
||||
internal data class OnboardingVisaAccessCodeUM(
|
||||
val step: Step = Step.Enter,
|
||||
val accessCodeFirst: TextFieldValue = TextFieldValue(""),
|
||||
val accessCodeSecond: TextFieldValue = TextFieldValue(""),
|
||||
|
|
|
|||
|
|
@ -0,0 +1,45 @@
|
|||
package com.tangem.features.onboarding.v2.visa.impl.child.approve
|
||||
|
||||
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.visa.impl.child.approve.model.OnboardingVisaApproveModel
|
||||
import com.tangem.features.onboarding.v2.visa.impl.child.approve.ui.OnboardingVisaApprove
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
internal class OnboardingVisaApproveComponent(
|
||||
appComponentContext: AppComponentContext,
|
||||
private val params: Params,
|
||||
) : ComposableContentComponent, AppComponentContext by appComponentContext {
|
||||
|
||||
private val model: OnboardingVisaApproveModel = getOrCreateModel()
|
||||
|
||||
init {
|
||||
componentScope.launch {
|
||||
model.onDone.collect { params.onDone() }
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
override fun Content(modifier: Modifier) {
|
||||
val state by model.uiState.collectAsStateWithLifecycle()
|
||||
|
||||
BackHandler(onBack = remember(this) { { params.onBack() } })
|
||||
|
||||
OnboardingVisaApprove(
|
||||
state = state,
|
||||
modifier = modifier,
|
||||
)
|
||||
}
|
||||
|
||||
data class Params(
|
||||
val onBack: () -> Unit,
|
||||
val onDone: () -> Unit,
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
package com.tangem.features.onboarding.v2.visa.impl.child.approve.model
|
||||
|
||||
import androidx.compose.runtime.Stable
|
||||
import com.tangem.core.decompose.di.ComponentScoped
|
||||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.features.onboarding.v2.visa.impl.child.approve.ui.state.OnboardingVisaApproveUM
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
|
||||
@Stable
|
||||
@ComponentScoped
|
||||
internal class OnboardingVisaApproveModel @Inject constructor(
|
||||
override val dispatchers: CoroutineDispatcherProvider,
|
||||
) : Model() {
|
||||
|
||||
private val _uiState = MutableStateFlow(getInitialState())
|
||||
|
||||
val uiState = _uiState.asStateFlow()
|
||||
val onDone = MutableSharedFlow<Unit>()
|
||||
|
||||
private fun getInitialState(): OnboardingVisaApproveUM {
|
||||
return OnboardingVisaApproveUM(
|
||||
onApproveClick = ::onApproveClick,
|
||||
)
|
||||
}
|
||||
|
||||
private fun onApproveClick() {
|
||||
// TODO
|
||||
modelScope.launch { onDone.emit(Unit) }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,83 @@
|
|||
package com.tangem.features.onboarding.v2.visa.impl.child.approve.ui
|
||||
|
||||
import androidx.compose.foundation.Image
|
||||
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.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.res.vectorResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.tangem.common.ui.navigationButtons.NavigationButton
|
||||
import com.tangem.common.ui.navigationButtons.NavigationPrimaryButton
|
||||
import com.tangem.core.ui.components.SpacerH
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
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.visa.impl.child.approve.ui.state.OnboardingVisaApproveUM
|
||||
|
||||
@Composable
|
||||
internal fun OnboardingVisaApprove(state: OnboardingVisaApproveUM, modifier: Modifier = Modifier) {
|
||||
Column(
|
||||
modifier
|
||||
.fillMaxSize()
|
||||
.navigationBarsPadding(),
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
.padding(top = 56.dp, bottom = 32.dp)
|
||||
.padding(horizontal = 34.dp),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
Image(
|
||||
imageVector = ImageVector.vectorResource(R.drawable.ic_tangem_card_grey),
|
||||
contentDescription = null,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
)
|
||||
|
||||
SpacerH(86.dp)
|
||||
|
||||
Text(
|
||||
text = "Prepare Tangem Wallet",
|
||||
style = TangemTheme.typography.h2,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
textAlign = TextAlign.Center,
|
||||
)
|
||||
|
||||
SpacerH(12.dp)
|
||||
|
||||
Text(
|
||||
text = "Prepare the Tangem card\nand tap to approve",
|
||||
style = TangemTheme.typography.body1,
|
||||
color = TangemTheme.colors.text.secondary,
|
||||
textAlign = TextAlign.Center,
|
||||
)
|
||||
}
|
||||
|
||||
NavigationPrimaryButton(
|
||||
modifier = Modifier
|
||||
.imePadding()
|
||||
.padding(start = 16.dp, end = 16.dp, bottom = 16.dp)
|
||||
.fillMaxWidth(),
|
||||
primaryButton = NavigationButton(
|
||||
textReference = TextReference.Str("Approve"),
|
||||
iconRes = R.drawable.ic_tangem_24,
|
||||
isIconVisible = true,
|
||||
onClick = state.onApproveClick,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun Preview() {
|
||||
TangemThemePreview {
|
||||
OnboardingVisaApprove(state = OnboardingVisaApproveUM())
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
package com.tangem.features.onboarding.v2.visa.impl.child.approve.ui.state
|
||||
|
||||
internal data class OnboardingVisaApproveUM(
|
||||
val onApproveClick: () -> Unit = {},
|
||||
)
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
package com.tangem.features.onboarding.v2.visa.impl.child.choosewallet
|
||||
|
||||
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.visa.impl.child.choosewallet.model.OnboardingVisaChooseWalletModel
|
||||
import com.tangem.features.onboarding.v2.visa.impl.child.choosewallet.ui.OnboardingVisaChooseWallet
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
|
||||
internal class OnboardingVisaChooseWalletComponent(
|
||||
appComponentContext: AppComponentContext,
|
||||
private val params: Params,
|
||||
) : ComposableContentComponent, AppComponentContext by appComponentContext {
|
||||
|
||||
private val model: OnboardingVisaChooseWalletModel = getOrCreateModel()
|
||||
|
||||
init {
|
||||
model.onEvent
|
||||
.onEach { params.onEvent(it) }
|
||||
.launchIn(componentScope)
|
||||
}
|
||||
|
||||
@Composable
|
||||
override fun Content(modifier: Modifier) {
|
||||
val state by model.uiState.collectAsStateWithLifecycle()
|
||||
|
||||
BackHandler(onBack = remember(this) { { params.onBack() } })
|
||||
|
||||
OnboardingVisaChooseWallet(
|
||||
modifier = modifier,
|
||||
state = state,
|
||||
)
|
||||
}
|
||||
|
||||
data class Params(
|
||||
val onBack: () -> Unit,
|
||||
val onEvent: (Event) -> Unit,
|
||||
) {
|
||||
enum class Event {
|
||||
TangemWallet, OtherWallet
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,59 @@
|
|||
package com.tangem.features.onboarding.v2.visa.impl.child.choosewallet.model
|
||||
|
||||
import androidx.compose.runtime.Stable
|
||||
import com.tangem.core.decompose.di.ComponentScoped
|
||||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.features.onboarding.v2.impl.R
|
||||
import com.tangem.features.onboarding.v2.visa.impl.child.choosewallet.OnboardingVisaChooseWalletComponent.Params.Event
|
||||
import com.tangem.features.onboarding.v2.visa.impl.child.choosewallet.ui.state.OnboardingVisaChooseWalletUM
|
||||
import com.tangem.features.onboarding.v2.visa.impl.child.choosewallet.ui.state.SelectableChainRowUM
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
|
||||
@Stable
|
||||
@ComponentScoped
|
||||
internal class OnboardingVisaChooseWalletModel @Inject constructor(
|
||||
override val dispatchers: CoroutineDispatcherProvider,
|
||||
) : Model() {
|
||||
|
||||
private val _uiState = MutableStateFlow(getInitialState())
|
||||
|
||||
val onEvent = MutableSharedFlow<Event>()
|
||||
val uiState = _uiState.asStateFlow()
|
||||
|
||||
private fun getInitialState(): OnboardingVisaChooseWalletUM {
|
||||
val selectedOption = SelectableChainRowUM(
|
||||
event = Event.TangemWallet,
|
||||
icon = R.drawable.ic_tangem_24,
|
||||
text = TextReference.Str("Tangem Wallet"),
|
||||
)
|
||||
|
||||
return OnboardingVisaChooseWalletUM(
|
||||
options = persistentListOf(
|
||||
selectedOption,
|
||||
SelectableChainRowUM(
|
||||
event = Event.OtherWallet,
|
||||
icon = R.drawable.ic_wallet_filled_24,
|
||||
text = TextReference.Str("Other Wallet"),
|
||||
),
|
||||
),
|
||||
selectedOption = selectedOption,
|
||||
onOptionSelected = { option -> _uiState.update { it.copy(selectedOption = option) } },
|
||||
onContinueClick = ::onContinueClicked,
|
||||
)
|
||||
}
|
||||
|
||||
private fun onContinueClicked() {
|
||||
modelScope.launch {
|
||||
val selectedOption = _uiState.value.selectedOption ?: return@launch
|
||||
onEvent.emit(selectedOption.event)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,174 @@
|
|||
package com.tangem.features.onboarding.v2.visa.impl.child.choosewallet.ui
|
||||
|
||||
import androidx.compose.foundation.border
|
||||
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.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.res.vectorResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.tangem.core.ui.components.PrimaryButton
|
||||
import com.tangem.core.ui.components.notifications.Notification
|
||||
import com.tangem.core.ui.components.notifications.NotificationConfig
|
||||
import com.tangem.core.ui.components.rows.RowContentContainer
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.outsetBorder
|
||||
import com.tangem.core.ui.extensions.resolveReference
|
||||
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.visa.impl.child.choosewallet.OnboardingVisaChooseWalletComponent
|
||||
import com.tangem.features.onboarding.v2.visa.impl.child.choosewallet.ui.state.OnboardingVisaChooseWalletUM
|
||||
import com.tangem.features.onboarding.v2.visa.impl.child.choosewallet.ui.state.SelectableChainRowUM
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
|
||||
@Composable
|
||||
internal fun OnboardingVisaChooseWallet(state: OnboardingVisaChooseWalletUM, modifier: Modifier = Modifier) {
|
||||
Column(
|
||||
modifier
|
||||
.fillMaxSize()
|
||||
.navigationBarsPadding(),
|
||||
) {
|
||||
Column(
|
||||
Modifier
|
||||
.weight(1f)
|
||||
.padding(top = 12.dp)
|
||||
.padding(horizontal = 16.dp),
|
||||
) {
|
||||
Notification(
|
||||
config = NotificationConfig(
|
||||
subtitle = TextReference.Str(
|
||||
"Please choose the wallet you started the registration" +
|
||||
" process with to sign the transaction for creating your account on the Blockchain.",
|
||||
),
|
||||
iconResId = R.drawable.ic_alert_circle_24,
|
||||
),
|
||||
iconTint = TangemTheme.colors.icon.accent,
|
||||
)
|
||||
|
||||
Text(
|
||||
modifier = Modifier
|
||||
.padding(
|
||||
top = 12.dp,
|
||||
bottom = 8.dp,
|
||||
start = 12.dp,
|
||||
end = 12.dp,
|
||||
),
|
||||
text = "Choose wallet",
|
||||
style = TangemTheme.typography.subtitle2,
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
)
|
||||
|
||||
SelectableChainRows(
|
||||
items = state.options,
|
||||
selected = state.selectedOption,
|
||||
onClick = state.onOptionSelected,
|
||||
)
|
||||
}
|
||||
|
||||
PrimaryButton(
|
||||
modifier = Modifier
|
||||
.imePadding()
|
||||
.padding(start = 16.dp, end = 16.dp, bottom = 16.dp)
|
||||
.fillMaxWidth(),
|
||||
text = "Continue",
|
||||
onClick = state.onContinueClick,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SelectableChainRows(
|
||||
items: ImmutableList<SelectableChainRowUM>,
|
||||
selected: SelectableChainRowUM?,
|
||||
onClick: (SelectableChainRowUM) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Column(modifier) {
|
||||
items.forEach { item ->
|
||||
SelectableChainRow(
|
||||
state = item,
|
||||
selected = item == selected,
|
||||
onClick = { onClick(item) },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SelectableChainRow(
|
||||
state: SelectableChainRowUM,
|
||||
selected: Boolean,
|
||||
onClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
RowContentContainer(
|
||||
modifier = modifier
|
||||
.heightIn(min = 48.dp)
|
||||
.outsetBorder(
|
||||
color = if (selected) TangemTheme.colors.icon.accent.copy(alpha = 0.15f) else Color.Transparent,
|
||||
width = 5.dp,
|
||||
shape = RoundedCornerShape(size = 18.dp),
|
||||
)
|
||||
.clip(TangemTheme.shapes.roundedCornersXMedium)
|
||||
.border(
|
||||
width = 1.dp,
|
||||
color = if (selected) TangemTheme.colors.icon.accent else Color.Transparent,
|
||||
shape = TangemTheme.shapes.roundedCornersXMedium,
|
||||
)
|
||||
.clickable(onClick = onClick)
|
||||
.padding(12.dp),
|
||||
icon = {
|
||||
Icon(
|
||||
imageVector = ImageVector.vectorResource(id = state.icon),
|
||||
tint = if (selected) TangemTheme.colors.icon.primary1 else TangemTheme.colors.icon.informative,
|
||||
contentDescription = null,
|
||||
)
|
||||
},
|
||||
text = {
|
||||
Text(
|
||||
text = state.text.resolveReference(),
|
||||
style = TangemTheme.typography.button,
|
||||
color = if (selected) TangemTheme.colors.text.primary1 else TangemTheme.colors.text.secondary,
|
||||
)
|
||||
},
|
||||
action = {},
|
||||
horizontalArrangement = Arrangement.spacedBy(12.dp),
|
||||
)
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun Preview() {
|
||||
TangemThemePreview {
|
||||
OnboardingVisaChooseWallet(
|
||||
state = OnboardingVisaChooseWalletUM(
|
||||
options = persistentListOf(
|
||||
SelectableChainRowUM(
|
||||
event = OnboardingVisaChooseWalletComponent.Params.Event.TangemWallet,
|
||||
icon = R.drawable.ic_tangem_24,
|
||||
text = TextReference.Str("Tangem Wallet"),
|
||||
),
|
||||
SelectableChainRowUM(
|
||||
event = OnboardingVisaChooseWalletComponent.Params.Event.OtherWallet,
|
||||
icon = R.drawable.ic_wallet_filled_24,
|
||||
text = TextReference.Str("Other Wallet"),
|
||||
),
|
||||
),
|
||||
selectedOption = SelectableChainRowUM(
|
||||
event = OnboardingVisaChooseWalletComponent.Params.Event.OtherWallet,
|
||||
icon = R.drawable.ic_tangem_24,
|
||||
text = TextReference.Str("Tangem Wallet"),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
package com.tangem.features.onboarding.v2.visa.impl.child.choosewallet.ui.state
|
||||
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
|
||||
internal data class OnboardingVisaChooseWalletUM(
|
||||
val options: ImmutableList<SelectableChainRowUM> = persistentListOf(),
|
||||
val selectedOption: SelectableChainRowUM? = null,
|
||||
val onOptionSelected: (SelectableChainRowUM) -> Unit = {},
|
||||
val onContinueClick: () -> Unit = {},
|
||||
)
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
package com.tangem.features.onboarding.v2.visa.impl.child.choosewallet.ui.state
|
||||
|
||||
import androidx.annotation.DrawableRes
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.features.onboarding.v2.visa.impl.child.choosewallet.OnboardingVisaChooseWalletComponent
|
||||
|
||||
internal data class SelectableChainRowUM(
|
||||
val event: OnboardingVisaChooseWalletComponent.Params.Event,
|
||||
@DrawableRes val icon: Int,
|
||||
val text: TextReference,
|
||||
)
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
package com.tangem.features.onboarding.v2.visa.impl.child.inprogress
|
||||
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
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.visa.impl.child.inprogress.model.OnboardingVisaInProgressModel
|
||||
import com.tangem.features.onboarding.v2.visa.impl.child.inprogress.ui.OnboardingVisaInProgress
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
|
||||
class OnboardingVisaInProgressComponent(
|
||||
appComponentContext: AppComponentContext,
|
||||
private val params: Params,
|
||||
) : ComposableContentComponent, AppComponentContext by appComponentContext {
|
||||
|
||||
private val model: OnboardingVisaInProgressModel = getOrCreateModel()
|
||||
|
||||
init {
|
||||
model.onDone
|
||||
.onEach { params.onDone() }
|
||||
.launchIn(componentScope)
|
||||
}
|
||||
|
||||
@Composable
|
||||
override fun Content(modifier: Modifier) {
|
||||
BackHandler(onBack = remember(this) { { params.onBack() } })
|
||||
|
||||
OnboardingVisaInProgress(
|
||||
modifier = modifier,
|
||||
)
|
||||
}
|
||||
|
||||
data class Params(
|
||||
val onBack: () -> Unit,
|
||||
val onDone: () -> Unit,
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
package com.tangem.features.onboarding.v2.visa.impl.child.inprogress.model
|
||||
|
||||
import androidx.compose.runtime.Stable
|
||||
import com.tangem.core.decompose.di.ComponentScoped
|
||||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
|
||||
@Stable
|
||||
@ComponentScoped
|
||||
internal class OnboardingVisaInProgressModel @Inject constructor(
|
||||
override val dispatchers: CoroutineDispatcherProvider,
|
||||
) : Model() {
|
||||
|
||||
val onDone = MutableSharedFlow<Unit>()
|
||||
|
||||
init {
|
||||
modelScope.launch {
|
||||
// TODO check state
|
||||
delay(timeMillis = 2000)
|
||||
onDone.emit(Unit)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
package com.tangem.features.onboarding.v2.visa.impl.child.inprogress.ui
|
||||
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.navigationBarsPadding
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
|
||||
@Composable
|
||||
internal fun OnboardingVisaInProgress(modifier: Modifier = Modifier) {
|
||||
Column(
|
||||
modifier
|
||||
.fillMaxSize()
|
||||
.navigationBarsPadding(),
|
||||
) {
|
||||
Column(
|
||||
Modifier
|
||||
.weight(1f)
|
||||
.padding(top = 12.dp)
|
||||
.padding(horizontal = 16.dp),
|
||||
) {
|
||||
// TODO
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun Preview() {
|
||||
TangemThemePreview {
|
||||
OnboardingVisaInProgress()
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
package com.tangem.features.onboarding.v2.visa.impl.child.otherwallet
|
||||
|
||||
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.visa.impl.child.otherwallet.model.OnboardingVisaOtherWalletModel
|
||||
import com.tangem.features.onboarding.v2.visa.impl.child.otherwallet.ui.OnboardingVisaOtherWallet
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
|
||||
internal class OnboardingVisaOtherWalletComponent(
|
||||
appComponentContext: AppComponentContext,
|
||||
private val params: Params,
|
||||
) : ComposableContentComponent, AppComponentContext by appComponentContext {
|
||||
|
||||
private val model: OnboardingVisaOtherWalletModel = getOrCreateModel()
|
||||
|
||||
init {
|
||||
model.onDone
|
||||
.onEach { params.onDone() }
|
||||
.launchIn(componentScope)
|
||||
}
|
||||
|
||||
@Composable
|
||||
override fun Content(modifier: Modifier) {
|
||||
val state by model.uiState.collectAsStateWithLifecycle()
|
||||
|
||||
BackHandler(onBack = remember(this) { { params.onBack() } })
|
||||
|
||||
OnboardingVisaOtherWallet(
|
||||
modifier = modifier,
|
||||
state = state,
|
||||
)
|
||||
}
|
||||
|
||||
data class Params(
|
||||
val onBack: () -> Unit,
|
||||
val onDone: () -> Unit,
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
package com.tangem.features.onboarding.v2.visa.impl.child.otherwallet.model
|
||||
|
||||
import androidx.compose.runtime.Stable
|
||||
import com.tangem.core.decompose.di.ComponentScoped
|
||||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.features.onboarding.v2.visa.impl.child.otherwallet.ui.state.OnboardingVisaOtherWalletUM
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import javax.inject.Inject
|
||||
|
||||
@Stable
|
||||
@ComponentScoped
|
||||
internal class OnboardingVisaOtherWalletModel @Inject constructor(
|
||||
override val dispatchers: CoroutineDispatcherProvider,
|
||||
) : Model() {
|
||||
|
||||
private val _uiState = MutableStateFlow(getInitialState())
|
||||
|
||||
val uiState = _uiState.asStateFlow()
|
||||
val onDone = MutableSharedFlow<Unit>()
|
||||
|
||||
private fun getInitialState(): OnboardingVisaOtherWalletUM {
|
||||
return OnboardingVisaOtherWalletUM(
|
||||
onShareClick = ::onShareClicked,
|
||||
onOpenInBrowserClick = ::onOpenInBrowserClicked,
|
||||
)
|
||||
}
|
||||
|
||||
private fun onShareClicked() {
|
||||
// TODO
|
||||
}
|
||||
|
||||
private fun onOpenInBrowserClicked() {
|
||||
// TODO
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,137 @@
|
|||
package com.tangem.features.onboarding.v2.visa.impl.child.otherwallet.ui
|
||||
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.res.vectorResource
|
||||
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.PrimaryButton
|
||||
import com.tangem.core.ui.components.SecondaryButton
|
||||
import com.tangem.core.ui.components.SpacerH12
|
||||
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.visa.impl.child.otherwallet.ui.state.OnboardingVisaOtherWalletUM
|
||||
|
||||
@Composable
|
||||
internal fun OnboardingVisaOtherWallet(state: OnboardingVisaOtherWalletUM, modifier: Modifier = Modifier) {
|
||||
Column(
|
||||
modifier
|
||||
.fillMaxSize()
|
||||
.navigationBarsPadding(),
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
.padding(top = 12.dp, bottom = 32.dp)
|
||||
.padding(horizontal = 16.dp),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
IconBlock()
|
||||
|
||||
SpacerH12()
|
||||
|
||||
Text(
|
||||
text = "Go to Website",
|
||||
style = TangemTheme.typography.h2,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
textAlign = TextAlign.Center,
|
||||
)
|
||||
|
||||
SpacerH12()
|
||||
|
||||
Text(
|
||||
text = "You will be able to complete your connection \n" +
|
||||
"on the third-party web-site \n" +
|
||||
"and back to the Tangem app",
|
||||
style = TangemTheme.typography.body1,
|
||||
color = TangemTheme.colors.text.secondary,
|
||||
textAlign = TextAlign.Center,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
PrimaryButton(
|
||||
modifier = Modifier
|
||||
.padding(horizontal = 16.dp)
|
||||
.fillMaxWidth(),
|
||||
text = "Open in Browser",
|
||||
onClick = state.onOpenInBrowserClick,
|
||||
)
|
||||
|
||||
SpacerH12()
|
||||
|
||||
SecondaryButton(
|
||||
modifier = Modifier
|
||||
.padding(start = 16.dp, end = 16.dp, bottom = 16.dp)
|
||||
.fillMaxWidth(),
|
||||
text = "Share Link",
|
||||
onClick = state.onOpenInBrowserClick,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun IconBlock(modifier: Modifier = Modifier) {
|
||||
Row(
|
||||
modifier = modifier,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(12.dp),
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.size(64.dp)
|
||||
.background(TangemTheme.colors.button.primary, TangemTheme.shapes.roundedCorners8),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
Icon(
|
||||
imageVector = ImageVector.vectorResource(id = R.drawable.ic_tangem_24),
|
||||
contentDescription = null,
|
||||
tint = TangemTheme.colors.icon.primary2,
|
||||
modifier = Modifier.size(38.dp),
|
||||
)
|
||||
}
|
||||
|
||||
Image(
|
||||
imageVector = ImageVector.vectorResource(id = R.drawable.ic_loader),
|
||||
contentDescription = null,
|
||||
modifier = Modifier.width(32.dp),
|
||||
)
|
||||
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.size(64.dp)
|
||||
.background(TangemTheme.colors.background.tertiary, TangemTheme.shapes.roundedCorners8),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
Icon(
|
||||
imageVector = ImageVector.vectorResource(id = R.drawable.ic_web_24),
|
||||
contentDescription = null,
|
||||
tint = TangemTheme.colors.icon.primary1,
|
||||
modifier = Modifier.size(48.dp),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun Preview() {
|
||||
TangemThemePreview {
|
||||
OnboardingVisaOtherWallet(OnboardingVisaOtherWalletUM())
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
package com.tangem.features.onboarding.v2.visa.impl.child.otherwallet.ui.state
|
||||
|
||||
internal data class OnboardingVisaOtherWalletUM(
|
||||
val onShareClick: () -> Unit = {},
|
||||
val onOpenInBrowserClick: () -> Unit = {},
|
||||
)
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
package com.tangem.features.onboarding.v2.visa.impl.child.pincode
|
||||
|
||||
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.core.ui.security.DisableScreenshotsDisposableEffect
|
||||
import com.tangem.features.onboarding.v2.visa.impl.child.pincode.model.OnboardingVisaPinCodeModel
|
||||
import com.tangem.features.onboarding.v2.visa.impl.child.pincode.ui.OnboardingVisaPinCode
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
internal class OnboardingVisaPinCodeComponent(
|
||||
appComponentContext: AppComponentContext,
|
||||
private val params: Params,
|
||||
) : ComposableContentComponent, AppComponentContext by appComponentContext {
|
||||
|
||||
private val model: OnboardingVisaPinCodeModel = getOrCreateModel()
|
||||
|
||||
init {
|
||||
componentScope.launch {
|
||||
model.onDone.collect { params.onDone() }
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
override fun Content(modifier: Modifier) {
|
||||
val state by model.uiState.collectAsStateWithLifecycle()
|
||||
|
||||
BackHandler(onBack = remember(this) { { params.onBack() } })
|
||||
|
||||
DisableScreenshotsDisposableEffect()
|
||||
|
||||
OnboardingVisaPinCode(
|
||||
modifier = modifier,
|
||||
state = state,
|
||||
)
|
||||
}
|
||||
|
||||
data class Params(
|
||||
val onBack: () -> Unit,
|
||||
val onDone: () -> Unit,
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
package com.tangem.features.onboarding.v2.visa.impl.child.pincode.model
|
||||
|
||||
import androidx.compose.runtime.Stable
|
||||
import com.tangem.core.decompose.di.ComponentScoped
|
||||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.features.onboarding.v2.visa.impl.child.pincode.ui.state.OnboardingVisaPinCodeUM
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
|
||||
@Stable
|
||||
@ComponentScoped
|
||||
internal class OnboardingVisaPinCodeModel @Inject constructor(
|
||||
override val dispatchers: CoroutineDispatcherProvider,
|
||||
) : Model() {
|
||||
|
||||
private val _uiState = MutableStateFlow(getInitialState())
|
||||
|
||||
val uiState = _uiState.asStateFlow()
|
||||
val onDone = MutableSharedFlow<Unit>()
|
||||
|
||||
private fun getInitialState(): OnboardingVisaPinCodeUM {
|
||||
return OnboardingVisaPinCodeUM(
|
||||
onPinCodeChange = ::onPinCodeChange,
|
||||
onSubmitClick = ::onSubmitClick,
|
||||
)
|
||||
}
|
||||
|
||||
private fun onPinCodeChange(pin: String) {
|
||||
if (pin.all { it.isDigit() }) _uiState.update { it.copy(pinCode = pin) }
|
||||
}
|
||||
|
||||
private fun onSubmitClick() {
|
||||
if (checkPinCode(_uiState.value.pinCode).not()) return
|
||||
|
||||
// TODO
|
||||
|
||||
modelScope.launch { onDone.emit(Unit) }
|
||||
}
|
||||
|
||||
private fun checkPinCode(pin: String): Boolean {
|
||||
return pin.length == PIN_CODE_LENGTH
|
||||
}
|
||||
|
||||
private companion object {
|
||||
const val PIN_CODE_LENGTH = 4
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,195 @@
|
|||
package com.tangem.features.onboarding.v2.visa.impl.child.pincode.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.text.BasicTextField
|
||||
import androidx.compose.foundation.text.KeyboardActions
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.alpha
|
||||
import androidx.compose.ui.focus.FocusRequester
|
||||
import androidx.compose.ui.focus.focusRequester
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.Color.Companion.Transparent
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.text.input.ImeAction
|
||||
import androidx.compose.ui.text.input.KeyboardType
|
||||
import androidx.compose.ui.text.rememberTextMeasurer
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.tangem.core.ui.components.PrimaryButton
|
||||
import com.tangem.core.ui.components.SpacerH
|
||||
import com.tangem.core.ui.components.SpacerH16
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.features.onboarding.v2.visa.impl.child.pincode.ui.state.OnboardingVisaPinCodeUM
|
||||
import kotlinx.coroutines.delay
|
||||
|
||||
@Composable
|
||||
internal fun OnboardingVisaPinCode(state: OnboardingVisaPinCodeUM, modifier: Modifier = Modifier) {
|
||||
Column(
|
||||
modifier = modifier
|
||||
.fillMaxSize()
|
||||
.navigationBarsPadding(),
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(top = 48.dp)
|
||||
.padding(horizontal = 36.dp)
|
||||
.weight(1f),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
Text(
|
||||
text = "Create\nPIN Code",
|
||||
style = TangemTheme.typography.h2,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
textAlign = TextAlign.Center,
|
||||
)
|
||||
|
||||
SpacerH16()
|
||||
|
||||
Text(
|
||||
text = "Set up a 4-digit code.\nIt will be used for payments.",
|
||||
style = TangemTheme.typography.body1,
|
||||
color = TangemTheme.colors.text.secondary,
|
||||
textAlign = TextAlign.Center,
|
||||
)
|
||||
|
||||
SpacerH(26.dp)
|
||||
|
||||
PinCodeSection(state)
|
||||
}
|
||||
|
||||
PrimaryButton(
|
||||
modifier = Modifier
|
||||
.imePadding()
|
||||
.padding(start = 16.dp, end = 16.dp, bottom = 16.dp)
|
||||
.fillMaxWidth(),
|
||||
text = "Submit",
|
||||
onClick = state.onSubmitClick,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun PinCodeSection(state: OnboardingVisaPinCodeUM, modifier: Modifier = Modifier) {
|
||||
val focusRequester = remember { FocusRequester() }
|
||||
|
||||
PinCode(
|
||||
modifier = modifier,
|
||||
value = state.pinCode,
|
||||
onValueChange = state.onPinCodeChange,
|
||||
focusRequester = focusRequester,
|
||||
)
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
delay(timeMillis = 300)
|
||||
focusRequester.requestFocus()
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun PinCode(
|
||||
value: String,
|
||||
onValueChange: (String) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
numbersCount: Int = 4,
|
||||
focusRequester: FocusRequester = remember { FocusRequester() },
|
||||
onDone: ((String) -> Unit)? = null,
|
||||
) {
|
||||
val keyboardController = LocalSoftwareKeyboardController.current
|
||||
|
||||
Box(
|
||||
contentAlignment = Alignment.Center,
|
||||
modifier = modifier,
|
||||
) {
|
||||
BasicTextField(
|
||||
value = value,
|
||||
onValueChange = { text ->
|
||||
if (text.length <= numbersCount) {
|
||||
onValueChange(text)
|
||||
if (text.length == numbersCount) {
|
||||
onDone?.invoke(text)
|
||||
}
|
||||
}
|
||||
},
|
||||
keyboardOptions = KeyboardOptions.Default.copy(
|
||||
keyboardType = KeyboardType.Number,
|
||||
imeAction = ImeAction.Done,
|
||||
),
|
||||
keyboardActions = KeyboardActions(onDone = {
|
||||
keyboardController?.hide()
|
||||
},),
|
||||
modifier = Modifier
|
||||
.alpha(alpha = 0.01f)
|
||||
.focusRequester(focusRequester),
|
||||
textStyle = TextStyle.Default.copy(color = Transparent),
|
||||
)
|
||||
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.clickable(
|
||||
indication = null,
|
||||
interactionSource = remember { MutableInteractionSource() },
|
||||
onClick = {
|
||||
keyboardController?.show()
|
||||
focusRequester.requestFocus()
|
||||
},
|
||||
),
|
||||
horizontalArrangement = Arrangement.spacedBy(12.dp),
|
||||
) {
|
||||
repeat(numbersCount) { index ->
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.background(
|
||||
color = Color(color = 0xFFF5F5F5),
|
||||
shape = TangemTheme.shapes.roundedCornersXMedium,
|
||||
)
|
||||
.padding(
|
||||
vertical = 12.dp,
|
||||
horizontal = 16.dp,
|
||||
),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
Text(
|
||||
modifier = Modifier.widthIn(min = measureTextWidth("0", TangemTheme.typography.h1)),
|
||||
text = value.getOrNull(index)?.toString() ?: "",
|
||||
style = TangemTheme.typography.h1,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun measureTextWidth(text: String, style: TextStyle): Dp {
|
||||
val textMeasurer = rememberTextMeasurer()
|
||||
val widthInPixels = textMeasurer.measure(text, style).size.width
|
||||
return with(LocalDensity.current) { widthInPixels.toDp() }
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun Preview() {
|
||||
TangemThemePreview {
|
||||
OnboardingVisaPinCode(
|
||||
state = OnboardingVisaPinCodeUM(
|
||||
pinCode = "1234",
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
package com.tangem.features.onboarding.v2.visa.impl.child.pincode.ui.state
|
||||
|
||||
internal data class OnboardingVisaPinCodeUM(
|
||||
val pinCode: String = "",
|
||||
val onPinCodeChange: (String) -> Unit = {},
|
||||
val onSubmitClick: () -> Unit = {},
|
||||
)
|
||||
|
|
@ -16,16 +16,29 @@ internal class OnboardingVisaWelcomeComponent(
|
|||
|
||||
@Composable
|
||||
override fun Content(modifier: Modifier) {
|
||||
val state = remember(this) {
|
||||
OnboardingVisaWelcomeUM(
|
||||
mode = if (params.isWelcomeBack) {
|
||||
OnboardingVisaWelcomeUM.Mode.WelcomeBack
|
||||
} else {
|
||||
OnboardingVisaWelcomeUM.Mode.Hello
|
||||
},
|
||||
userName = "User", // TODO:
|
||||
onContinueClick = params.onDone,
|
||||
)
|
||||
}
|
||||
|
||||
BackHandler(onBack = remember(this) { { params.onBack() } })
|
||||
|
||||
OnboardingVisaWelcome(
|
||||
modifier = modifier,
|
||||
state = OnboardingVisaWelcomeUM(),
|
||||
state = state,
|
||||
)
|
||||
}
|
||||
|
||||
data class Params(
|
||||
val isWelcomeBack: Boolean,
|
||||
val onBack: () -> Unit,
|
||||
val startActivation: () -> Unit,
|
||||
val onDone: () -> Unit,
|
||||
)
|
||||
}
|
||||
|
|
@ -1,14 +1,16 @@
|
|||
package com.tangem.features.onboarding.v2.visa.impl.child.welcome.ui
|
||||
|
||||
import androidx.compose.foundation.Canvas
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.layout.onSizeChanged
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import coil.compose.AsyncImage
|
||||
import com.tangem.core.ui.components.PrimaryButton
|
||||
import com.tangem.core.ui.components.PrimaryButtonIconEnd
|
||||
import com.tangem.core.ui.components.SpacerH
|
||||
|
|
@ -87,10 +89,12 @@ internal fun OnboardingVisaWelcome(state: OnboardingVisaWelcomeUM, modifier: Mod
|
|||
}
|
||||
}
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
@Composable
|
||||
fun Artwork(modifier: Modifier = Modifier) {
|
||||
Box(modifier) {
|
||||
val circleColor = TangemTheme.colors.background.secondary
|
||||
var cardHeightPx by remember { mutableIntStateOf(0) }
|
||||
|
||||
Canvas(
|
||||
modifier = Modifier
|
||||
|
|
@ -100,17 +104,19 @@ fun Artwork(modifier: Modifier = Modifier) {
|
|||
) {
|
||||
drawCircle(
|
||||
color = circleColor,
|
||||
radius = size.width / 2,
|
||||
radius = cardHeightPx / 2 + cardHeightPx / 8f,
|
||||
center = center,
|
||||
)
|
||||
}
|
||||
|
||||
AsyncImage(
|
||||
model = R.drawable.img_card_visa,
|
||||
Image(
|
||||
painter = painterResource(R.drawable.img_card_visa),
|
||||
contentDescription = null,
|
||||
modifier = Modifier
|
||||
modifier = modifier
|
||||
.align(Alignment.Center)
|
||||
.size(128.dp), // Adjust the size as needed
|
||||
.onSizeChanged { cardHeightPx = it.height }
|
||||
.widthIn(max = 512.dp)
|
||||
.fillMaxWidth(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,22 @@
|
|||
package com.tangem.features.onboarding.v2.visa.impl.di
|
||||
|
||||
import com.tangem.core.decompose.di.DecomposeComponent
|
||||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.features.onboarding.v2.visa.api.OnboardingVisaComponent
|
||||
import com.tangem.features.onboarding.v2.visa.impl.DefaultOnboardingVisaComponent
|
||||
import com.tangem.features.onboarding.v2.visa.impl.child.accesscode.model.OnboardingVisaAccessCodeModel
|
||||
import com.tangem.features.onboarding.v2.visa.impl.child.approve.model.OnboardingVisaApproveModel
|
||||
import com.tangem.features.onboarding.v2.visa.impl.child.choosewallet.model.OnboardingVisaChooseWalletModel
|
||||
import com.tangem.features.onboarding.v2.visa.impl.child.inprogress.model.OnboardingVisaInProgressModel
|
||||
import com.tangem.features.onboarding.v2.visa.impl.child.otherwallet.model.OnboardingVisaOtherWalletModel
|
||||
import com.tangem.features.onboarding.v2.visa.impl.child.pincode.model.OnboardingVisaPinCodeModel
|
||||
import com.tangem.features.onboarding.v2.visa.impl.model.OnboardingVisaModel
|
||||
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 +26,44 @@ internal interface ComponentModule {
|
|||
@Binds
|
||||
@Singleton
|
||||
fun bindComponentFactory(factory: DefaultOnboardingVisaComponent.Factory): OnboardingVisaComponent.Factory
|
||||
}
|
||||
|
||||
@Module
|
||||
@InstallIn(DecomposeComponent::class)
|
||||
internal interface ModelModule {
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@ClassKey(OnboardingVisaAccessCodeModel::class)
|
||||
fun provideModel(model: OnboardingVisaAccessCodeModel): Model
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@ClassKey(OnboardingVisaChooseWalletModel::class)
|
||||
fun provideModel2(model: OnboardingVisaChooseWalletModel): Model
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@ClassKey(OnboardingVisaPinCodeModel::class)
|
||||
fun provideModel3(model: OnboardingVisaPinCodeModel): Model
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@ClassKey(OnboardingVisaApproveModel::class)
|
||||
fun provideModel4(model: OnboardingVisaApproveModel): Model
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@ClassKey(OnboardingVisaOtherWalletModel::class)
|
||||
fun provideModel5(model: OnboardingVisaOtherWalletModel): Model
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@ClassKey(OnboardingVisaInProgressModel::class)
|
||||
fun provideModel6(model: OnboardingVisaInProgressModel): Model
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@ClassKey(OnboardingVisaModel::class)
|
||||
fun provideModel7(model: OnboardingVisaModel): Model
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
package com.tangem.features.onboarding.v2.visa.impl.model
|
||||
|
||||
import androidx.compose.runtime.Stable
|
||||
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.onboarding.v2.visa.api.OnboardingVisaComponent
|
||||
import com.tangem.features.onboarding.v2.visa.impl.route.OnboardingVisaRoute
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import javax.inject.Inject
|
||||
|
||||
@Stable
|
||||
@ComponentScoped
|
||||
internal class OnboardingVisaModel @Inject constructor(
|
||||
paramsContainer: ParamsContainer,
|
||||
override val dispatchers: CoroutineDispatcherProvider,
|
||||
) : Model() {
|
||||
|
||||
@Suppress("UnusedPrivateMember")
|
||||
private val params = paramsContainer.require<OnboardingVisaComponent.Params>()
|
||||
|
||||
val initialRoute = OnboardingVisaRoute.Welcome(isWelcomeBack = false)
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
package com.tangem.features.onboarding.v2.visa.impl.route
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
sealed class OnboardingVisaRoute {
|
||||
|
||||
@Serializable
|
||||
data class Welcome(val isWelcomeBack: Boolean) : OnboardingVisaRoute()
|
||||
|
||||
@Serializable
|
||||
data object AccessCode : OnboardingVisaRoute()
|
||||
|
||||
@Serializable
|
||||
data object ChooseWallet : OnboardingVisaRoute()
|
||||
|
||||
@Serializable
|
||||
data object TangemWalletApproveOption : OnboardingVisaRoute()
|
||||
|
||||
@Serializable
|
||||
data object OtherWalletApproveOption : OnboardingVisaRoute()
|
||||
|
||||
@Serializable
|
||||
data object InProgress : OnboardingVisaRoute()
|
||||
|
||||
@Serializable
|
||||
data object PinCode : OnboardingVisaRoute()
|
||||
}
|
||||
|
|
@ -2,11 +2,11 @@ package com.tangem.feature.wallet.presentation.wallet.ui.utils
|
|||
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.content.ContextWrapper
|
||||
import com.google.android.gms.tasks.Task
|
||||
import com.google.android.play.core.review.ReviewInfo
|
||||
import com.google.android.play.core.review.ReviewManager
|
||||
import com.google.android.play.core.review.ReviewManagerFactory
|
||||
import com.tangem.core.ui.utils.findActivity
|
||||
import timber.log.Timber
|
||||
|
||||
internal object ReviewManagerRequester {
|
||||
|
|
@ -44,13 +44,4 @@ internal object ReviewManagerRequester {
|
|||
Timber.e(task.exception)
|
||||
}
|
||||
}
|
||||
|
||||
private fun Context.findActivity(): Activity {
|
||||
var context = this
|
||||
while (context is ContextWrapper) {
|
||||
if (context is Activity) return context
|
||||
context = context.baseContext
|
||||
}
|
||||
error("Permissions should be called in the context of an Activity")
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue