Updated on 2026-08-14

This commit is contained in:
Tangem 2025-06-27 16:18:02 +03:00
commit 702e0b2efc
12 changed files with 472 additions and 1 deletions

View file

@ -157,9 +157,10 @@ private fun WalletBlock(
.padding(16.dp)
.clickable(onClick = onClick),
) {
Row(verticalAlignment = Alignment.CenterVertically) {
Row {
Text(
modifier = Modifier
.weight(1f)
.padding(end = 8.dp),
text = title,
style = TangemTheme.typography.subtitle1,

View file

@ -0,0 +1,8 @@
package com.tangem.features.hotwallet
import com.tangem.core.decompose.factory.ComponentFactory
import com.tangem.core.ui.decompose.ComposableContentComponent
interface AddExistingWalletComponent : ComposableContentComponent {
interface Factory : ComponentFactory<Unit, AddExistingWalletComponent>
}

View file

@ -0,0 +1,25 @@
package com.tangem.features.hotwallet.addexistingwallet.root
import com.tangem.core.decompose.di.ModelScoped
import com.tangem.core.decompose.model.Model
import com.tangem.features.hotwallet.addexistingwallet.start.AddExistingWalletStartComponent
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import javax.inject.Inject
@ModelScoped
internal class AddExistingWalletModel @Inject constructor(
override val dispatchers: CoroutineDispatcherProvider,
) : Model() {
val addExistingWalletStartModelCallbacks = AddExistingWalletStartModelCallbacks()
inner class AddExistingWalletStartModelCallbacks : AddExistingWalletStartComponent.ModelCallbacks {
override fun onBackClick() {
// TODO implement in [REDACTED_TASK_KEY]
}
override fun onImportPhraseClick() {
// TODO implement in [REDACTED_TASK_KEY]
}
}
}

View file

@ -0,0 +1,72 @@
package com.tangem.features.hotwallet.addexistingwallet.root
import androidx.activity.compose.BackHandler
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import com.arkivanov.decompose.extensions.compose.subscribeAsState
import com.arkivanov.decompose.router.stack.StackNavigation
import com.arkivanov.decompose.router.stack.childStack
import com.arkivanov.decompose.router.stack.pop
import com.tangem.core.decompose.context.AppComponentContext
import com.tangem.core.decompose.context.childByContext
import com.tangem.core.decompose.model.getOrCreateModel
import com.tangem.features.hotwallet.AddExistingWalletComponent
import com.tangem.features.hotwallet.addexistingwallet.root.routing.AddExistingWalletChildFactory
import com.tangem.features.hotwallet.addexistingwallet.root.routing.AddExistingWalletRoute
import com.tangem.features.hotwallet.addexistingwallet.root.ui.AddExistingWalletContent
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
internal class DefaultAddExistingWalletComponent @AssistedInject constructor(
@Assisted appComponentContext: AppComponentContext,
@Assisted private val params: Unit,
addExistingWalletChildFactory: AddExistingWalletChildFactory,
) : AddExistingWalletComponent, AppComponentContext by appComponentContext {
private val model: AddExistingWalletModel = getOrCreateModel(params)
private val stackNavigation = StackNavigation<AddExistingWalletRoute>()
private val startRoute = AddExistingWalletRoute.Start
private val innerStack = childStack(
key = "addExistingWalletInnerStack",
source = stackNavigation,
serializer = null,
initialConfiguration = startRoute,
handleBackButton = true,
childFactory = { configuration, factoryContext ->
addExistingWalletChildFactory.createChild(
route = configuration,
childContext = childByContext(factoryContext),
model = model,
)
},
)
@Composable
override fun Content(modifier: Modifier) {
val stackState by innerStack.subscribeAsState()
BackHandler(onBack = ::onChildBack)
AddExistingWalletContent(
stackState = stackState,
)
}
private fun onChildBack() {
val isEmptyStack = innerStack.value.backStack.isEmpty()
if (isEmptyStack) {
router.pop()
} else {
stackNavigation.pop()
}
}
@AssistedFactory
interface Factory : AddExistingWalletComponent.Factory {
override fun create(context: AppComponentContext, params: Unit): DefaultAddExistingWalletComponent
}
}

View file

@ -0,0 +1,5 @@
package com.tangem.features.hotwallet.addexistingwallet.root.entity
internal data class AddExistingWalletUM(
val onBackClick: () -> Unit,
)

View file

@ -0,0 +1,25 @@
package com.tangem.features.hotwallet.addexistingwallet.root.routing
import com.tangem.core.decompose.context.AppComponentContext
import com.tangem.core.ui.decompose.ComposableContentComponent
import com.tangem.features.hotwallet.addexistingwallet.root.AddExistingWalletModel
import com.tangem.features.hotwallet.addexistingwallet.start.AddExistingWalletStartComponent
import javax.inject.Inject
internal class AddExistingWalletChildFactory @Inject constructor() {
fun createChild(
route: AddExistingWalletRoute,
childContext: AppComponentContext,
model: AddExistingWalletModel,
): ComposableContentComponent {
return when (route) {
is AddExistingWalletRoute.Start -> AddExistingWalletStartComponent(
context = childContext,
params = AddExistingWalletStartComponent.Params(
callbacks = model.addExistingWalletStartModelCallbacks,
),
)
}
}
}

View file

@ -0,0 +1,10 @@
package com.tangem.features.hotwallet.addexistingwallet.root.routing
import com.tangem.core.decompose.navigation.Route
import kotlinx.serialization.Serializable
internal sealed class AddExistingWalletRoute : Route {
@Serializable
object Start : AddExistingWalletRoute()
}

View file

@ -0,0 +1,37 @@
package com.tangem.features.hotwallet.addexistingwallet.root.ui
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.imePadding
import androidx.compose.foundation.layout.systemBarsPadding
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import com.arkivanov.decompose.extensions.compose.stack.Children
import com.arkivanov.decompose.extensions.compose.stack.animation.slide
import com.arkivanov.decompose.extensions.compose.stack.animation.stackAnimation
import com.arkivanov.decompose.router.stack.ChildStack
import com.tangem.core.ui.decompose.ComposableContentComponent
import com.tangem.core.ui.res.TangemTheme
import com.tangem.features.hotwallet.addexistingwallet.root.routing.AddExistingWalletRoute
@Composable
internal fun AddExistingWalletContent(stackState: ChildStack<AddExistingWalletRoute, ComposableContentComponent>) {
Column(
modifier = Modifier.Companion
.background(color = TangemTheme.colors.background.primary)
.fillMaxSize()
.imePadding()
.systemBarsPadding(),
horizontalAlignment = Alignment.Companion.CenterHorizontally,
) {
Children(
stack = stackState,
animation = stackAnimation(slide()),
modifier = Modifier.weight(1f),
) {
it.instance.Content(Modifier.weight(1f))
}
}
}

View file

@ -0,0 +1,37 @@
package com.tangem.features.hotwallet.addexistingwallet.start
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
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.hotwallet.addexistingwallet.start.ui.AddExistingWalletStartContent
import dagger.assisted.Assisted
import dagger.assisted.AssistedInject
internal class AddExistingWalletStartComponent @AssistedInject constructor(
@Assisted private val context: AppComponentContext,
@Assisted private val params: Params,
) : ComposableContentComponent, AppComponentContext by context {
private val model: AddExistingWalletStartModel = getOrCreateModel(params)
@Composable
override fun Content(modifier: Modifier) {
val state by model.uiState.collectAsStateWithLifecycle()
AddExistingWalletStartContent(
state = state,
modifier = modifier,
)
}
interface ModelCallbacks {
fun onBackClick()
fun onImportPhraseClick()
}
data class Params(
val callbacks: ModelCallbacks,
)
}

View file

@ -0,0 +1,29 @@
package com.tangem.features.hotwallet.addexistingwallet.start
import com.tangem.core.decompose.di.ModelScoped
import com.tangem.core.decompose.model.Model
import com.tangem.core.decompose.model.ParamsContainer
import com.tangem.features.hotwallet.addexistingwallet.start.entity.AddExistingWalletStartUM
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import javax.inject.Inject
@ModelScoped
internal class AddExistingWalletStartModel @Inject constructor(
paramsContainer: ParamsContainer,
override val dispatchers: CoroutineDispatcherProvider,
) : Model() {
private val params: AddExistingWalletStartComponent.Params = paramsContainer.require()
internal val uiState: StateFlow<AddExistingWalletStartUM>
field = MutableStateFlow(
AddExistingWalletStartUM(
onBackClick = params.callbacks::onBackClick,
onImportPhraseClick = params.callbacks::onImportPhraseClick,
onScanCardClick = { /* [REDACTED_TODO_COMMENT] */ },
onBuyCardClick = { /* [REDACTED_TODO_COMMENT] */ },
),
)
}

View file

@ -0,0 +1,8 @@
package com.tangem.features.hotwallet.addexistingwallet.start.entity
internal data class AddExistingWalletStartUM(
val onBackClick: () -> Unit,
val onImportPhraseClick: () -> Unit,
val onScanCardClick: () -> Unit,
val onBuyCardClick: () -> Unit,
)

View file

@ -0,0 +1,214 @@
package com.tangem.features.hotwallet.addexistingwallet.start.ui
import android.content.res.Configuration
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.material3.*
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.tangem.core.ui.components.PrimaryButton
import com.tangem.core.ui.components.appbar.TangemTopAppBar
import com.tangem.core.ui.components.appbar.models.TopAppBarButtonUM
import com.tangem.core.ui.components.buttons.common.TangemButtonSize
import com.tangem.core.ui.components.buttons.common.TangemButtonsDefaults
import com.tangem.core.ui.extensions.clickableSingle
import com.tangem.core.ui.extensions.conditional
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.features.hotwallet.addexistingwallet.start.entity.AddExistingWalletStartUM
@Suppress("LongMethod")
@OptIn(ExperimentalMaterial3Api::class)
@Composable
internal fun AddExistingWalletStartContent(state: AddExistingWalletStartUM, modifier: Modifier = Modifier) {
Column(
modifier = Modifier
.background(TangemTheme.colors.background.primary)
.fillMaxSize()
.systemBarsPadding(),
) {
TangemTopAppBar(
modifier = modifier
.statusBarsPadding(),
startButton = TopAppBarButtonUM.Back(state.onBackClick),
title = "Add existing wallet",
)
Column(
modifier = Modifier
.weight(1f)
.padding(
start = 16.dp,
top = 24.dp,
end = 16.dp,
),
) {
Text(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp),
// TODO [REDACTED_TASK_KEY] update and extract this string
text = "Import wallet",
style = TangemTheme.typography.h2,
color = TangemTheme.colors.text.primary1,
textAlign = TextAlign.Center,
)
OptionBlock(
modifier = Modifier
.padding(top = 24.dp),
// TODO [REDACTED_TASK_KEY] update and extract this string
title = "Import a recovery phrase",
// TODO [REDACTED_TASK_KEY] update and extract this string
description = "Your 12-word seed phrase to recover your wallet",
badge = null,
onClick = state.onImportPhraseClick,
enabled = true,
)
OptionBlock(
// TODO [REDACTED_TASK_KEY] update and extract this string
title = "Scan a Tangem Wallet",
// TODO [REDACTED_TASK_KEY] update and extract this string
description = "Physical cards that securely store your crypto offline.",
badge = null,
onClick = state.onScanCardClick,
enabled = true,
)
OptionBlock(
// TODO [REDACTED_TASK_KEY] update and extract this string
title = "Import from Google Drive",
// TODO [REDACTED_TASK_KEY] update and extract this string
description = "Recover an existing wallet stored in your Google Drive backup",
badge = {
Box(
modifier = Modifier
.background(
color = TangemTheme.colors.field.focused,
shape = TangemTheme.shapes.roundedCorners8,
)
.padding(horizontal = 8.dp, vertical = 4.dp),
) {
Text(
// TODO [REDACTED_TASK_KEY] update and extract this string
text = "Coming Soon",
style = TangemTheme.typography.caption1,
color = TangemTheme.colors.text.tertiary,
)
}
},
onClick = null,
enabled = false,
)
}
BuyTangemWalletBlock(
onScanClick = state.onBuyCardClick,
)
}
}
@Composable
private fun OptionBlock(
title: String,
description: String,
badge: (@Composable () -> Unit)?,
onClick: (() -> Unit)?,
enabled: Boolean,
modifier: Modifier = Modifier,
) {
Column(
modifier = modifier
.fillMaxWidth()
.padding(top = 8.dp)
.background(
color = TangemTheme.colors.background.secondary,
shape = TangemTheme.shapes.roundedCornersXMedium,
)
.padding(16.dp)
.conditional(onClick != null) {
onClick?.let { clickableSingle(onClick = it) } ?: Modifier
},
) {
Row {
Text(
modifier = Modifier
.weight(1f)
.padding(end = 8.dp),
text = title,
style = TangemTheme.typography.subtitle1,
color = if (enabled) {
TangemTheme.colors.text.primary1
} else {
TangemTheme.colors.text.secondary
},
)
badge?.invoke()
}
Text(
modifier = Modifier
.padding(top = 4.dp),
text = description,
style = TangemTheme.typography.body2,
color = if (enabled) {
TangemTheme.colors.text.tertiary
} else {
TangemTheme.colors.text.disabled
},
)
}
}
@Composable
private fun BuyTangemWalletBlock(onScanClick: () -> Unit, modifier: Modifier = Modifier) {
Row(
modifier = modifier
.fillMaxWidth()
.padding(16.dp)
.background(
color = TangemTheme.colors.field.primary,
shape = TangemTheme.shapes.roundedCornersXMedium,
)
.padding(
horizontal = 20.dp,
vertical = 16.dp,
),
verticalAlignment = Alignment.CenterVertically,
) {
Text(
modifier = Modifier
.weight(1f)
.padding(end = 16.dp),
// TODO [REDACTED_TASK_KEY] update and extract this string
text = "Want to purchase a Tangem Wallet?",
style = TangemTheme.typography.button,
color = TangemTheme.colors.text.primary1,
)
PrimaryButton(
modifier = Modifier
.wrapContentWidth(),
// TODO [REDACTED_TASK_KEY] update and extract this string
text = "Buy Now",
onClick = onScanClick,
size = TangemButtonSize.RoundedAction,
colors = TangemButtonsDefaults.secondaryButtonColors,
enabled = true,
)
}
}
@Preview(showBackground = true, widthDp = 360)
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun PreviewCreateWalletContent() {
TangemThemePreview {
AddExistingWalletStartContent(
state = AddExistingWalletStartUM(
onBackClick = {},
onImportPhraseClick = {},
onScanCardClick = {},
onBuyCardClick = {},
),
)
}
}