diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/bottomsheets/TangemBottomSheet.kt b/core/ui/src/main/java/com/tangem/core/ui/components/bottomsheets/TangemBottomSheet.kt index d9b884cb75..22399d6e4b 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/bottomsheets/TangemBottomSheet.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/bottomsheets/TangemBottomSheet.kt @@ -1,16 +1,21 @@ package com.tangem.core.ui.components.bottomsheets +import androidx.activity.compose.BackHandler +import androidx.activity.compose.LocalOnBackPressedDispatcherOwner import androidx.compose.foundation.background +import androidx.compose.foundation.focusable import androidx.compose.foundation.layout.* -import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.ModalBottomSheet -import androidx.compose.material3.SheetState +import androidx.compose.material3.* import androidx.compose.material3.SheetValue.Expanded -import androidx.compose.material3.rememberModalBottomSheetState import androidx.compose.runtime.* import androidx.compose.ui.Modifier +import androidx.compose.ui.focus.FocusRequester +import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.Shape +import androidx.compose.ui.input.key.* import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import com.tangem.core.ui.components.appbar.models.TopAppBarButtonUM import com.tangem.core.ui.extensions.TextReference @@ -18,6 +23,7 @@ import com.tangem.core.ui.res.LocalBottomSheetAlwaysVisible import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.utils.WindowInsetsZero import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.delay import kotlinx.coroutines.launch /** @@ -31,6 +37,7 @@ inline fun TangemBottomSheet( containerColor: Color = TangemTheme.colors.background.primary, addBottomInsets: Boolean = true, skipPartiallyExpanded: Boolean = true, + noinline onBack: (() -> Unit)? = null, crossinline content: @Composable ColumnScope.(T) -> Unit, ) { TangemBottomSheet( @@ -39,6 +46,7 @@ inline fun TangemBottomSheet( addBottomInsets = addBottomInsets, title = { TangemBottomSheetTitle(title = titleText, endButton = titleAction) }, skipPartiallyExpanded = skipPartiallyExpanded, + onBack = onBack, content = content, ) } @@ -52,6 +60,7 @@ inline fun TangemBottomSheet( containerColor: Color = TangemTheme.colors.background.primary, addBottomInsets: Boolean = true, skipPartiallyExpanded: Boolean = true, + noinline onBack: (() -> Unit)? = null, crossinline title: @Composable BoxScope.(T) -> Unit = {}, crossinline content: @Composable ColumnScope.(T) -> Unit, ) { @@ -73,6 +82,7 @@ inline fun TangemBottomSheet( addBottomInsets = addBottomInsets, title = title, content = content, + onBack = onBack, skipPartiallyExpanded = skipPartiallyExpanded, ) } @@ -85,6 +95,7 @@ inline fun DefaultBottomSheet( containerColor: Color, addBottomInsets: Boolean, skipPartiallyExpanded: Boolean = true, + noinline onBack: (() -> Unit)? = null, crossinline title: @Composable (BoxScope.(T) -> Unit), crossinline content: @Composable (ColumnScope.(T) -> Unit), ) { @@ -98,6 +109,7 @@ inline fun DefaultBottomSheet( containerColor = containerColor, addBottomInsets = addBottomInsets, title = title, + onBack = onBack, content = content, ) } @@ -129,6 +141,7 @@ inline fun PreviewBottomSheet( initialValue = Expanded, density = LocalDensity.current, ), + onBack = null, containerColor = containerColor, addBottomInsets = addBottomInsets, title = title, @@ -144,6 +157,7 @@ inline fun BasicBottomSheet( sheetState: SheetState, containerColor: Color, addBottomInsets: Boolean, + noinline onBack: (() -> Unit)? = null, crossinline title: @Composable (BoxScope.(T) -> Unit), crossinline content: @Composable (ColumnScope.(T) -> Unit), modifier: Modifier = Modifier, @@ -152,15 +166,7 @@ inline fun BasicBottomSheet( val bottomBarHeight = with(LocalDensity.current) { WindowInsets.systemBars.getBottom(this).toDp() } - ModalBottomSheet( - modifier = modifier.statusBarsPadding(), - onDismissRequest = config.onDismissRequest, - sheetState = sheetState, - containerColor = containerColor, - shape = TangemTheme.shapes.bottomSheetLarge, - contentWindowInsets = { WindowInsetsZero }, - dragHandle = { TangemBottomSheetDraggableHeader(color = containerColor) }, - ) { + val bsContent: @Composable ColumnScope.() -> Unit = { Column( modifier = Modifier.let { if (addBottomInsets) { @@ -179,6 +185,90 @@ inline fun BasicBottomSheet( content(model) } } + + if (onBack != null) { + ModalBottomSheetWithBackHandling( + modifier = modifier.statusBarsPadding(), + onDismissRequest = config.onDismissRequest, + sheetState = sheetState, + containerColor = containerColor, + shape = TangemTheme.shapes.bottomSheetLarge, + contentWindowInsets = { WindowInsetsZero }, + dragHandle = { TangemBottomSheetDraggableHeader(color = containerColor) }, + onBack = onBack, + content = bsContent, + ) + } else { + ModalBottomSheet( + modifier = modifier.statusBarsPadding(), + onDismissRequest = config.onDismissRequest, + sheetState = sheetState, + containerColor = containerColor, + shape = TangemTheme.shapes.bottomSheetLarge, + contentWindowInsets = { WindowInsetsZero }, + dragHandle = { TangemBottomSheetDraggableHeader(color = containerColor) }, + content = bsContent, + ) + } +} + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun ModalBottomSheetWithBackHandling( + onDismissRequest: () -> Unit, + onBack: (() -> Unit), + modifier: Modifier = Modifier, + sheetState: SheetState = rememberModalBottomSheetState(), + sheetMaxWidth: Dp = BottomSheetDefaults.SheetMaxWidth, + shape: Shape = BottomSheetDefaults.ExpandedShape, + containerColor: Color = BottomSheetDefaults.ContainerColor, + contentColor: Color = contentColorFor(containerColor), + tonalElevation: Dp = 0.dp, + scrimColor: Color = BottomSheetDefaults.ScrimColor, + dragHandle: @Composable (() -> Unit)? = { BottomSheetDefaults.DragHandle() }, + contentWindowInsets: @Composable () -> WindowInsets = { BottomSheetDefaults.windowInsets }, + content: @Composable ColumnScope.() -> Unit, +) { + BackHandler(enabled = sheetState.targetValue != SheetValue.Hidden) { + onBack() + } + + val requester = remember { FocusRequester() } + val backPressedDispatcherOwner = LocalOnBackPressedDispatcherOwner.current + + ModalBottomSheet( + onDismissRequest = onDismissRequest, + modifier = modifier + .focusRequester(requester) + .focusable() + .onPreviewKeyEvent { + if (it.key == Key.Back && it.type == KeyEventType.KeyUp && !it.nativeKeyEvent.isCanceled) { + backPressedDispatcherOwner?.onBackPressedDispatcher?.onBackPressed() + return@onPreviewKeyEvent true + } + return@onPreviewKeyEvent false + }, + sheetState = sheetState, + sheetMaxWidth = sheetMaxWidth, + shape = shape, + containerColor = containerColor, + contentColor = contentColor, + tonalElevation = tonalElevation, + scrimColor = scrimColor, + dragHandle = dragHandle, + contentWindowInsets = contentWindowInsets, + properties = ModalBottomSheetDefaults.properties( + // Set false otherwise the onPreviewKeyEvent doesn't work at all. + // The functionality of shouldDismissOnBackPress is achieved by the BackHandler. + shouldDismissOnBackPress = false, + ), + content = content, + ) + + LaunchedEffect(Unit) { + delay(timeMillis = 200) + requester.requestFocus() + } } @OptIn(ExperimentalMaterial3Api::class) diff --git a/core/ui/src/main/java/com/tangem/core/ui/res/TangemAnimations.kt b/core/ui/src/main/java/com/tangem/core/ui/res/TangemAnimations.kt index 2e81db1003..28d531895d 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/res/TangemAnimations.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/res/TangemAnimations.kt @@ -7,8 +7,6 @@ import androidx.compose.runtime.* @Immutable object TangemAnimations { - val transitionSpecs = TransitionSpecs - @Composable @NonRestartableComposable fun horizontalIndicatorAsState(targetFraction: Float): State { @@ -19,8 +17,18 @@ object TangemAnimations { ) } - @Immutable - object TransitionSpecs { - // TODO add more transition specs + object AnimatedContent { + fun slide( + forwardWhen: (initial: S, target: S) -> Boolean, + ): AnimatedContentTransitionScope.() -> ContentTransform = { + val direction = if (forwardWhen(initialState, targetState)) { + AnimatedContentTransitionScope.SlideDirection.End + } else { + AnimatedContentTransitionScope.SlideDirection.Start + } + + slideIntoContainer(towards = direction, animationSpec = tween()) + .togetherWith(slideOutOfContainer(towards = direction, animationSpec = tween())) + } } } \ No newline at end of file diff --git a/core/ui/src/main/res/drawable/ic_access_code.xml b/core/ui/src/main/res/drawable/ic_access_code.xml new file mode 100644 index 0000000000..d24768aa15 --- /dev/null +++ b/core/ui/src/main/res/drawable/ic_access_code.xml @@ -0,0 +1,24 @@ + + + + + + + + diff --git a/core/ui/src/main/res/drawable/ic_feature_1.xml b/core/ui/src/main/res/drawable/ic_feature_1.xml new file mode 100644 index 0000000000..ed4eba060f --- /dev/null +++ b/core/ui/src/main/res/drawable/ic_feature_1.xml @@ -0,0 +1,22 @@ + + + + + diff --git a/core/ui/src/main/res/drawable/ic_feature_2.xml b/core/ui/src/main/res/drawable/ic_feature_2.xml new file mode 100644 index 0000000000..6c1b54564b --- /dev/null +++ b/core/ui/src/main/res/drawable/ic_feature_2.xml @@ -0,0 +1,22 @@ + + + + + diff --git a/core/ui/src/main/res/drawable/ic_feature_3.xml b/core/ui/src/main/res/drawable/ic_feature_3.xml new file mode 100644 index 0000000000..cf8a08f7d0 --- /dev/null +++ b/core/ui/src/main/res/drawable/ic_feature_3.xml @@ -0,0 +1,22 @@ + + + + + diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/DefaultOnboardingMultiWalletComponent.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/DefaultOnboardingMultiWalletComponent.kt index 876f7de961..138110ba57 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/DefaultOnboardingMultiWalletComponent.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/DefaultOnboardingMultiWalletComponent.kt @@ -10,6 +10,10 @@ import com.arkivanov.decompose.extensions.compose.jetpack.stack.animation.fade 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.slot.SlotNavigation +import com.arkivanov.decompose.router.slot.childSlot +import com.arkivanov.decompose.router.slot.dismiss +import com.arkivanov.decompose.router.slot.navigate import com.arkivanov.decompose.router.stack.* import com.arkivanov.decompose.value.Value import com.arkivanov.essenty.instancekeeper.getOrCreateSimple @@ -21,6 +25,7 @@ import com.tangem.core.decompose.navigation.inner.InnerNavigationState import com.tangem.features.onboarding.v2.multiwallet.api.OnboardingMultiWalletComponent import com.tangem.features.onboarding.v2.multiwallet.impl.child.MultiWalletChildComponent import com.tangem.features.onboarding.v2.multiwallet.impl.child.MultiWalletChildParams +import com.tangem.features.onboarding.v2.multiwallet.impl.child.accesscode.MultiWalletAccessCodeComponent import com.tangem.features.onboarding.v2.multiwallet.impl.child.backup.MultiWalletBackupComponent import com.tangem.features.onboarding.v2.multiwallet.impl.child.chooseoption.Wallet1ChooseOptionComponent import com.tangem.features.onboarding.v2.multiwallet.impl.child.createwallet.MultiWalletCreateWalletComponent @@ -56,6 +61,11 @@ internal class DefaultOnboardingMultiWalletComponent @AssistedInject constructor ) } + private val childParams = MultiWalletChildParams( + multiWalletState = model.state, + parentParams = params, + ) + override val innerNavigation: InnerNavigation = object : InnerNavigation { override val state = MutableStateFlow( MultiWalletInnerNavigationState(1, 5), @@ -82,6 +92,24 @@ internal class DefaultOnboardingMultiWalletComponent @AssistedInject constructor }, ) + private val bottomSheetNavigation = SlotNavigation() + private val accessCodeBottomSheetSlot = childSlot( + source = bottomSheetNavigation, + serializer = null, + handleBackButton = false, + childFactory = { configuration, componentContext -> + MultiWalletAccessCodeComponent( + context = childByContext(componentContext), + params = childParams, + onDismiss = { bottomSheetNavigation.dismiss() }, + ) + }, + ) + + init { + bottomSheetNavigation.navigate {} + } + private fun createChild( step: OnboardingMultiWalletState.Step, childContext: AppComponentContext, @@ -121,7 +149,7 @@ internal class DefaultOnboardingMultiWalletComponent @AssistedInject constructor } } - fun handleNavigationEvent(nextStep: OnboardingMultiWalletState.Step) { + private fun handleNavigationEvent(nextStep: OnboardingMultiWalletState.Step) { when (nextStep) { ChooseBackupOption -> { artworksState.value = WalletArtworksState.Fan @@ -141,7 +169,7 @@ internal class DefaultOnboardingMultiWalletComponent @AssistedInject constructor stackNavigation.push(nextStep) } - fun handleBackupComponentEvent(event: MultiWalletBackupComponent.Event) { + private fun handleBackupComponentEvent(event: MultiWalletBackupComponent.Event) { when (event) { MultiWalletBackupComponent.Event.Done -> { // TODO navigate to finalize @@ -184,6 +212,9 @@ internal class DefaultOnboardingMultiWalletComponent @AssistedInject constructor } }, ) + + val bottomSheetState by accessCodeBottomSheetSlot.subscribeAsState() + bottomSheetState.child?.instance?.BottomSheet() } @AssistedFactory diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/accesscode/MultiWalletAccessCodeComponent.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/accesscode/MultiWalletAccessCodeComponent.kt new file mode 100644 index 0000000000..6f5d5748f4 --- /dev/null +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/accesscode/MultiWalletAccessCodeComponent.kt @@ -0,0 +1,58 @@ +package com.tangem.features.onboarding.v2.multiwallet.impl.child.accesscode + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.tangem.core.decompose.context.AppComponentContext +import com.tangem.core.decompose.model.getOrCreateModel +import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig +import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent +import com.tangem.core.ui.decompose.ComposableBottomSheetComponent +import com.tangem.features.onboarding.v2.multiwallet.impl.child.MultiWalletChildParams +import com.tangem.features.onboarding.v2.multiwallet.impl.child.accesscode.model.MultiWalletAccessCodeModel +import com.tangem.features.onboarding.v2.multiwallet.impl.child.accesscode.ui.MultiWalletAccessCodeBS +import kotlinx.coroutines.launch + +@Suppress("UnusedPrivateMember") +class MultiWalletAccessCodeComponent( + context: AppComponentContext, + private val params: MultiWalletChildParams, + private val onDismiss: () -> Unit, +) : AppComponentContext by context, ComposableBottomSheetComponent { + + private val model = getOrCreateModel() + + init { + componentScope.launch { + model.onDismiss.collect { + onDismiss() + } + } + } + + override fun dismiss() { + onDismiss() + } + + @Composable + override fun BottomSheet() { + val state by model.uiState.collectAsStateWithLifecycle() + val bsConfig = remember(this) { + TangemBottomSheetConfig( + isShow = true, + onDismissRequest = onDismiss, + content = TangemBottomSheetConfigContent.Empty, + ) + } + + MultiWalletAccessCodeBS( + config = bsConfig, + state = state, + onBack = { model.onBack() }, + ) + } + + @JvmInline + value class AccessCode(val value: String) +} \ No newline at end of file diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/accesscode/model/MultiWalletAccessCodeModel.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/accesscode/model/MultiWalletAccessCodeModel.kt new file mode 100644 index 0000000000..ecef852fa0 --- /dev/null +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/accesscode/model/MultiWalletAccessCodeModel.kt @@ -0,0 +1,60 @@ +package com.tangem.features.onboarding.v2.multiwallet.impl.child.accesscode.model + +import com.tangem.core.decompose.di.ComponentScoped +import com.tangem.core.decompose.model.Model +import com.tangem.features.onboarding.v2.multiwallet.impl.child.accesscode.ui.state.MultiWalletAccessCodeUM +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 + +@ComponentScoped +internal class MultiWalletAccessCodeModel @Inject constructor( + override val dispatchers: CoroutineDispatcherProvider, +) : Model() { + + private val _uiState = MutableStateFlow(getInitialState()) + + val uiState = _uiState.asStateFlow() + val onDismiss = MutableSharedFlow() + + fun onBack() { + when (_uiState.value.step) { + MultiWalletAccessCodeUM.Step.Intro -> { + modelScope.launch { onDismiss.emit(Unit) } + } + MultiWalletAccessCodeUM.Step.AccessCode -> { + _uiState.update { + it.copy(step = MultiWalletAccessCodeUM.Step.Intro) + } + } + MultiWalletAccessCodeUM.Step.ConfirmAccessCode -> { + _uiState.update { + it.copy(step = MultiWalletAccessCodeUM.Step.AccessCode) + } + } + } + } + + private fun getInitialState() = MultiWalletAccessCodeUM( + onContinue = ::onContinue, + ) + + private fun onContinue() { + when (_uiState.value.step) { + MultiWalletAccessCodeUM.Step.Intro -> { + _uiState.update { it.copy(step = MultiWalletAccessCodeUM.Step.AccessCode) } + } + MultiWalletAccessCodeUM.Step.AccessCode -> { + _uiState.update { it.copy(step = MultiWalletAccessCodeUM.Step.ConfirmAccessCode) } + } + MultiWalletAccessCodeUM.Step.ConfirmAccessCode -> { + // TODO check + modelScope.launch { onDismiss.emit(Unit) } + } + } + } +} \ No newline at end of file diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/accesscode/ui/MultiWalletAccessCodeBS.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/accesscode/ui/MultiWalletAccessCodeBS.kt new file mode 100644 index 0000000000..ca6f0b7c5f --- /dev/null +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/accesscode/ui/MultiWalletAccessCodeBS.kt @@ -0,0 +1,75 @@ +package com.tangem.features.onboarding.v2.multiwallet.impl.child.accesscode.ui + +import androidx.compose.animation.AnimatedContent +import androidx.compose.foundation.layout.* +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import com.tangem.core.ui.components.PrimaryButton +import com.tangem.core.ui.components.bottomsheets.TangemBottomSheet +import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig +import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent +import com.tangem.core.ui.res.TangemAnimations +import com.tangem.features.onboarding.v2.impl.R +import com.tangem.features.onboarding.v2.multiwallet.impl.child.accesscode.ui.state.MultiWalletAccessCodeUM +import com.tangem.features.onboarding.v2.multiwallet.impl.child.accesscode.ui.state.MultiWalletAccessCodeUM.Step.* + +@Composable +internal fun MultiWalletAccessCodeBS( + config: TangemBottomSheetConfig, + state: MultiWalletAccessCodeUM, + onBack: () -> Unit, +) { + TangemBottomSheet( + config = config, + onBack = onBack, + ) { _ -> + Content(state) + } +} + +@Composable +private fun Content(state: MultiWalletAccessCodeUM, modifier: Modifier = Modifier) { + Column( + modifier = modifier + .fillMaxSize(), + ) { + AnimatedContent( + modifier = Modifier.weight(1f), + targetState = state.step, + transitionSpec = TangemAnimations.AnimatedContent + .slide { initial, target -> initial.ordinal > target.ordinal }, + label = "AnimatedContent", + ) { step -> + when (step) { + Intro -> MultiWalletAccessCodeIntro( + modifier = Modifier.padding(top = 56.dp), + ) + AccessCode -> MultiWalletAccessCodeEnter( + modifier = Modifier.padding(top = 16.dp), + state = state, + reEnterAccessCodeState = false, + ) + ConfirmAccessCode -> MultiWalletAccessCodeEnter( + modifier = Modifier.padding(top = 16.dp), + state = state, + reEnterAccessCodeState = true, + ) + } + } + + PrimaryButton( + modifier = Modifier + .fillMaxWidth() + .padding(start = 16.dp, end = 16.dp, bottom = 16.dp) + .imePadding(), + text = if (state.step == ConfirmAccessCode) { + stringResource(R.string.common_confirm) + } else { + stringResource(R.string.common_continue) + }, + onClick = state.onContinue, + ) + } +} \ No newline at end of file diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/accesscode/ui/MultiWalletAccessCodeEnter.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/accesscode/ui/MultiWalletAccessCodeEnter.kt new file mode 100644 index 0000000000..ff0ac3f8ea --- /dev/null +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/accesscode/ui/MultiWalletAccessCodeEnter.kt @@ -0,0 +1,89 @@ +package com.tangem.features.onboarding.v2.multiwallet.impl.child.accesscode.ui + +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.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.tangem.core.ui.components.OutlineTextField +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.child.accesscode.ui.state.MultiWalletAccessCodeUM + +@Composable +internal fun MultiWalletAccessCodeEnter( + state: MultiWalletAccessCodeUM, + reEnterAccessCodeState: Boolean, + modifier: Modifier = Modifier, +) { + Column( + modifier = modifier + .padding(horizontal = 16.dp), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy(32.dp), + ) { + Text( + text = if (reEnterAccessCodeState) { + stringResource(R.string.onboarding_access_code_repeat_code_title) + } else { + stringResource(R.string.onboarding_access_code_intro_title) + }, + style = TangemTheme.typography.subtitle1, + ) + Text( + text = if (reEnterAccessCodeState) { + stringResource(R.string.onboarding_access_code_repeat_code_hint) + } else { + stringResource(R.string.onboarding_access_code_hint) + }, + style = TangemTheme.typography.body1, + ) + + OutlineTextField( + modifier = modifier.fillMaxWidth(), + value = if (reEnterAccessCodeState) { + state.accessCodeSecond + } else { + state.accessCodeFirst + }, + onValueChange = if (reEnterAccessCodeState) { + state.onAccessCodeSecondChange + } else { + state.onAccessCodeFirstChange + }, + label = stringResource(id = R.string.onboarding_wallet_info_title_third), + isError = state.codesNotMatchError, + caption = if (state.codesNotMatchError && reEnterAccessCodeState) { + stringResource(R.string.onboarding_access_codes_doesnt_match) + } else { + null + }, + ) + } +} + +@Preview(showBackground = true) +@Composable +private fun Preview() { + TangemThemePreview { + MultiWalletAccessCodeEnter( + reEnterAccessCodeState = false, + state = MultiWalletAccessCodeUM(), + ) + } +} + +@Preview(showBackground = true) +@Composable +private fun Preview2() { + TangemThemePreview { + MultiWalletAccessCodeEnter( + reEnterAccessCodeState = true, + state = MultiWalletAccessCodeUM(), + ) + } +} \ No newline at end of file diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/accesscode/ui/MultiWalletAccessCodeIntro.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/accesscode/ui/MultiWalletAccessCodeIntro.kt new file mode 100644 index 0000000000..3172592edb --- /dev/null +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/accesscode/ui/MultiWalletAccessCodeIntro.kt @@ -0,0 +1,91 @@ +package com.tangem.features.onboarding.v2.multiwallet.impl.child.accesscode.ui + +import androidx.annotation.DrawableRes +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.stringResource +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.SpacerH +import com.tangem.core.ui.components.SpacerW +import com.tangem.core.ui.res.TangemTheme +import com.tangem.core.ui.res.TangemThemePreview +import com.tangem.features.onboarding.v2.impl.R + +@Composable +internal fun MultiWalletAccessCodeIntro(modifier: Modifier = Modifier) { + Column( + modifier = modifier, + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy(12.dp), + ) { + Text( + text = stringResource(R.string.onboarding_access_code_intro_title), + style = TangemTheme.typography.h2, + ) + Icon( + imageVector = ImageVector.vectorResource(R.drawable.ic_access_code), + tint = TangemTheme.colors.icon.informative, + contentDescription = null, + ) + Column( + modifier = Modifier + .padding(horizontal = 46.dp) + .fillMaxWidth(), + verticalArrangement = Arrangement.spacedBy(24.dp), + ) { + DescriptionItem( + title = stringResource(R.string.onboarding_access_code_feature_1_title), + body = stringResource(R.string.onboarding_access_code_feature_1_description), + iconRes = R.drawable.ic_feature_1, + ) + DescriptionItem( + title = stringResource(R.string.onboarding_access_code_feature_2_title), + body = stringResource(R.string.onboarding_access_code_feature_2_description), + iconRes = R.drawable.ic_feature_2, + ) + DescriptionItem( + title = stringResource(R.string.onboarding_access_code_feature_3_title), + body = stringResource(R.string.onboarding_access_code_feature_3_description), + iconRes = R.drawable.ic_feature_3, + ) + } + } +} + +@Composable +private fun DescriptionItem(title: String, body: String, @DrawableRes iconRes: Int, modifier: Modifier = Modifier) { + Row(modifier) { + Icon( + imageVector = ImageVector.vectorResource(iconRes), + tint = TangemTheme.colors.icon.primary1, + contentDescription = null, + ) + SpacerW(20.dp) + Column { + Text( + text = title, + style = TangemTheme.typography.subtitle1, + ) + SpacerH(3.dp) + Text( + text = body, + style = TangemTheme.typography.body2, + ) + } + } +} + +@Preview(showBackground = true) +@Composable +private fun Preview() { + TangemThemePreview { + MultiWalletAccessCodeIntro() + } +} \ No newline at end of file diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/accesscode/ui/state/MultiWalletAccessCodeUM.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/accesscode/ui/state/MultiWalletAccessCodeUM.kt new file mode 100644 index 0000000000..17db94bafd --- /dev/null +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/accesscode/ui/state/MultiWalletAccessCodeUM.kt @@ -0,0 +1,19 @@ +package com.tangem.features.onboarding.v2.multiwallet.impl.child.accesscode.ui.state + +import androidx.compose.ui.text.input.TextFieldValue + +internal data class MultiWalletAccessCodeUM( + val step: Step = Step.Intro, + val accessCodeFirst: TextFieldValue = TextFieldValue(""), + val accessCodeSecond: TextFieldValue = TextFieldValue(""), + val codesNotMatchError: Boolean = false, + val onAccessCodeFirstChange: (TextFieldValue) -> Unit = {}, + val onAccessCodeSecondChange: (TextFieldValue) -> Unit = {}, + val onContinue: () -> Unit = {}, +) { + enum class Step { + Intro, + AccessCode, + ConfirmAccessCode, + } +} \ No newline at end of file diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/seedphrase/ui/MultiWalletSeedPhrase.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/seedphrase/ui/MultiWalletSeedPhrase.kt index 5f2ea4aa18..985a7b68e4 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/seedphrase/ui/MultiWalletSeedPhrase.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/seedphrase/ui/MultiWalletSeedPhrase.kt @@ -1,12 +1,10 @@ package com.tangem.features.onboarding.v2.multiwallet.impl.child.seedphrase.ui import androidx.compose.animation.AnimatedContent -import androidx.compose.animation.AnimatedContentTransitionScope -import androidx.compose.animation.core.tween -import androidx.compose.animation.togetherWith import androidx.compose.foundation.layout.navigationBarsPadding import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier +import com.tangem.core.ui.res.TangemAnimations import com.tangem.features.onboarding.v2.multiwallet.impl.child.seedphrase.ui.state.MultiWalletSeedPhraseUM @Composable @@ -14,16 +12,7 @@ internal fun MultiWalletSeedPhrase(state: MultiWalletSeedPhraseUM, modifier: Mod AnimatedContent( modifier = modifier.navigationBarsPadding(), targetState = state, - transitionSpec = { - val direction = if (initialState.order < targetState.order) { - AnimatedContentTransitionScope.SlideDirection.Start - } else { - AnimatedContentTransitionScope.SlideDirection.End - } - - slideIntoContainer(towards = direction, animationSpec = tween()) - .togetherWith(slideOutOfContainer(towards = direction, animationSpec = tween())) - }, + transitionSpec = TangemAnimations.AnimatedContent.slide { from, to -> to.order > from.order }, contentKey = { st -> st::class.java }, label = "animatedContent", ) { st -> diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/di/ComponentModule.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/di/ComponentModule.kt index d1df99ba69..a1cc040ecf 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/di/ComponentModule.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/di/ComponentModule.kt @@ -4,6 +4,7 @@ import com.tangem.core.decompose.di.DecomposeComponent import com.tangem.core.decompose.model.Model import com.tangem.features.onboarding.v2.multiwallet.api.OnboardingMultiWalletComponent import com.tangem.features.onboarding.v2.multiwallet.impl.DefaultOnboardingMultiWalletComponent +import com.tangem.features.onboarding.v2.multiwallet.impl.child.accesscode.model.MultiWalletAccessCodeModel import com.tangem.features.onboarding.v2.multiwallet.impl.child.backup.model.MultiWalletBackupModel import com.tangem.features.onboarding.v2.multiwallet.impl.child.createwallet.model.MultiWalletCreateWalletModel import com.tangem.features.onboarding.v2.multiwallet.impl.child.seedphrase.model.MultiWalletSeedPhraseModel @@ -48,4 +49,9 @@ internal interface ModelModule { @IntoMap @ClassKey(MultiWalletSeedPhraseModel::class) fun provideModel4(model: MultiWalletSeedPhraseModel): Model + + @Binds + @IntoMap + @ClassKey(MultiWalletAccessCodeModel::class) + fun provideModel5(model: MultiWalletAccessCodeModel): Model } \ No newline at end of file