Updated on 2026-08-14

This commit is contained in:
Tangem 2025-10-10 13:51:10 +05:00
parent 499351d5a9
commit 2bc8c29f6d
31 changed files with 1086 additions and 7 deletions

View file

@ -0,0 +1 @@
/build

View file

@ -0,0 +1,72 @@
plugins {
alias(deps.plugins.android.library)
alias(deps.plugins.kotlin.android)
alias(deps.plugins.kotlin.kapt)
alias(deps.plugins.kotlin.serialization)
alias(deps.plugins.hilt.android)
id("configuration")
}
android {
namespace = "com.tangem.features.createwalletstart.impl"
}
dependencies {
/** Api */
implementation(projects.features.createWalletStart.api)
/** Project - Domain */
implementation(projects.domain.card)
implementation(projects.domain.settings)
implementation(projects.domain.wallets)
implementation(projects.domain.models)
/** Core modules */
implementation(projects.core.configToggles)
implementation(projects.core.analytics)
implementation(projects.core.analytics.models)
implementation(projects.core.utils)
implementation(projects.core.ui)
implementation(projects.core.res)
implementation(projects.core.decompose)
implementation(projects.core.navigation)
implementation(projects.core.datasource)
/** Common */
implementation(projects.common.ui)
implementation(projects.common.routing)
/** Tangem libraries */
implementation(projects.libs.tangemSdkApi)
implementation(tangemDeps.card.core)
implementation(tangemDeps.card.android) {
exclude(module = "joda-time")
}
/** AndroidX libraries */
implementation(deps.androidx.core.ktx)
implementation(deps.lifecycle.runtime.ktx)
/** Compose libraries */
implementation(deps.compose.material3)
implementation(deps.compose.animation)
implementation(deps.compose.foundation)
implementation(deps.compose.ui)
implementation(deps.compose.ui.tooling)
implementation(deps.compose.coil)
implementation(deps.lottie.compose)
implementation(deps.decompose.ext.compose)
implementation(deps.androidx.activity.compose)
implementation(deps.androidx.datastore)
/** Other libraries */
implementation(deps.arrow.core)
implementation(deps.kotlin.immutable.collections)
implementation(deps.kotlin.serialization)
implementation(deps.timber)
implementation(deps.firebase.crashlytics)
/** DI */
implementation(deps.hilt.android)
kapt(deps.hilt.kapt)
}

View file

@ -0,0 +1,243 @@
package com.tangem.features.createwalletstart
import com.tangem.common.core.TangemError
import com.tangem.common.core.TangemSdkError
import com.tangem.common.routing.AppRoute
import com.tangem.common.routing.AppRouter
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.analytics.models.AnalyticsParam
import com.tangem.core.analytics.models.Basic.SignedIn
import com.tangem.core.analytics.models.Basic.SignedIn.SignInType
import com.tangem.core.decompose.di.GlobalUiMessageSender
import com.tangem.core.decompose.di.ModelScoped
import com.tangem.core.decompose.model.Model
import com.tangem.core.decompose.model.ParamsContainer
import com.tangem.core.decompose.navigation.Router
import com.tangem.core.decompose.ui.UiMessageSender
import com.tangem.core.navigation.url.UrlOpener
import com.tangem.core.ui.R
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.message.DialogMessage
import com.tangem.domain.card.ScanCardProcessor
import com.tangem.domain.card.analytics.ParamCardCurrencyConverter
import com.tangem.domain.card.common.util.cardTypesResolver
import com.tangem.domain.card.repository.CardSdkConfigRepository
import com.tangem.domain.common.wallets.UserWalletsListRepository
import com.tangem.domain.common.wallets.error.SaveWalletError
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.domain.settings.repositories.SettingsRepository
import com.tangem.domain.wallets.builder.ColdUserWalletBuilder
import com.tangem.domain.wallets.usecase.GenerateBuyTangemCardLinkUseCase
import com.tangem.domain.wallets.usecase.SaveWalletUseCase
import com.tangem.features.createwalletstart.entity.CreateWalletStartUM
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.collections.immutable.persistentListOf
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import timber.log.Timber
import javax.inject.Inject
private const val HIDE_PROGRESS_DELAY = 400L
@Suppress("LongParameterList")
@ModelScoped
internal class CreateWalletStartModel @Inject constructor(
paramsContainer: ParamsContainer,
override val dispatchers: CoroutineDispatcherProvider,
private val router: Router,
private val scanCardProcessor: ScanCardProcessor,
private val cardSdkConfigRepository: CardSdkConfigRepository,
private val settingsRepository: SettingsRepository,
private val analyticsEventHandler: AnalyticsEventHandler,
private val appRouter: AppRouter,
private val coldUserWalletBuilderFactory: ColdUserWalletBuilder.Factory,
private val saveWalletUseCase: SaveWalletUseCase,
private val userWalletsListRepository: UserWalletsListRepository,
@GlobalUiMessageSender private val uiMessageSender: UiMessageSender,
private val generateBuyTangemCardLinkUseCase: GenerateBuyTangemCardLinkUseCase,
private val urlOpener: UrlOpener,
) : Model() {
private val params = paramsContainer.require<CreateWalletStartComponent.Params>()
internal val uiState: StateFlow<CreateWalletStartUM>
field = MutableStateFlow(
when (params.mode) {
CreateWalletStartComponent.Mode.ColdWallet -> CreateWalletStartUM(
title = resourceReference(R.string.common_tangem_wallet),
description = resourceReference(R.string.welcome_create_wallet_hardware_description),
featureItems = persistentListOf(
CreateWalletStartUM.FeatureItem(
iconResId = R.drawable.ic_shield_check_16,
text = resourceReference(R.string.welcome_create_wallet_feature_class),
),
CreateWalletStartUM.FeatureItem(
iconResId = R.drawable.ic_flash_16,
text = resourceReference(R.string.welcome_create_wallet_feature_delivery),
),
CreateWalletStartUM.FeatureItem(
iconResId = R.drawable.ic_sparkles_16,
text = resourceReference(R.string.welcome_create_wallet_feature_use),
),
),
imageResId = R.drawable.img_hardware_wallet,
showScanSecondaryButton = true,
onPrimaryButtonClick = ::onBuyClick,
primaryButtonText = resourceReference(R.string.details_buy_wallet),
otherMethodTitle = resourceReference(R.string.welcome_create_wallet_mobile_title),
otherMethodDescription = resourceReference(R.string.welcome_create_wallet_mobile_description),
otherMethodClick = ::onStartWithMobileWalletClick,
onBackClick = { router.pop() },
onScanClick = ::onScanClick,
isScanInProgress = false,
)
CreateWalletStartComponent.Mode.HotWallet -> CreateWalletStartUM(
title = resourceReference(R.string.hw_mobile_wallet),
description = resourceReference(R.string.welcome_create_wallet_mobile_description_full),
featureItems = persistentListOf(
CreateWalletStartUM.FeatureItem(
iconResId = R.drawable.ic_shield_check_16,
text = resourceReference(R.string.welcome_create_wallet_feature_seamless),
),
CreateWalletStartUM.FeatureItem(
iconResId = R.drawable.ic_flash_16,
text = resourceReference(R.string.welcome_create_wallet_feature_one_tap),
),
CreateWalletStartUM.FeatureItem(
iconResId = R.drawable.ic_stack_fill_new_16,
text = resourceReference(R.string.welcome_create_wallet_feature_assets),
),
),
imageResId = R.drawable.img_mobile_wallet,
showScanSecondaryButton = false,
onPrimaryButtonClick = ::onStartWithMobileWalletClick,
primaryButtonText = resourceReference(R.string.welcome_create_wallet_mobile_title),
otherMethodTitle = resourceReference(R.string.welcome_create_wallet_use_hardware_title),
otherMethodDescription = resourceReference(R.string.welcome_create_wallet_use_hardware_description),
otherMethodClick = ::onBuyClick,
onBackClick = { router.pop() },
onScanClick = ::onScanClick,
isScanInProgress = false,
)
},
)
private fun onScanClick() {
scanCard()
}
private fun onStartWithMobileWalletClick() {
router.push(AppRoute.CreateMobileWallet)
}
private fun onBuyClick() {
modelScope.launch {
generateBuyTangemCardLinkUseCase.invoke().let { urlOpener.openUrl(it) }
}
}
private fun scanCard() {
modelScope.launch {
setLoading(true)
val shouldSaveAccessCodes = settingsRepository.shouldSaveAccessCodes()
cardSdkConfigRepository.setAccessCodeRequestPolicy(
isBiometricsRequestPolicy = shouldSaveAccessCodes,
)
val analyticsSource = AnalyticsParam.ScreensSources.Intro
scanCardProcessor.scan(
analyticsSource = analyticsSource,
onProgressStateChange = { showProgress ->
if (!showProgress) {
delay(HIDE_PROGRESS_DELAY)
setLoading(false)
} else {
setLoading(true)
}
},
onFailure = { error ->
handleScanError(error)
delay(HIDE_PROGRESS_DELAY)
setLoading(false)
},
onSuccess = { scanResponse ->
proceedWithScanResponse(scanResponse)
},
)
}
}
private suspend fun proceedWithScanResponse(scanResponse: ScanResponse) {
val userWallet = coldUserWalletBuilderFactory.create(scanResponse = scanResponse).build()
if (userWallet == null) {
Timber.e("User wallet not created")
setLoading(false)
return
}
saveWalletUseCase(userWallet = userWallet).fold(
ifLeft = {
delay(HIDE_PROGRESS_DELAY)
setLoading(false)
when (it) {
is SaveWalletError.DataError -> Timber.e(it.toString(), "Unable to save user wallet")
is SaveWalletError.WalletAlreadySaved -> {
userWalletsListRepository.unlock(
userWalletId = userWallet.walletId,
unlockMethod = UserWalletsListRepository.UnlockMethod.Scan(scanResponse),
).onRight {
appRouter.replaceAll(AppRoute.Wallet)
}
}
}
},
ifRight = {
setLoading(false)
sendSignedInCardAnalyticsEvent(scanResponse)
appRouter.replaceAll(AppRoute.Wallet)
},
)
}
private suspend fun sendSignedInCardAnalyticsEvent(scanResponse: ScanResponse) {
val currency = ParamCardCurrencyConverter().convert(value = scanResponse.cardTypesResolver)
if (currency != null) {
analyticsEventHandler.send(
SignedIn(
currency = currency,
batch = scanResponse.card.batchId,
signInType = SignInType.Card,
walletsCount = userWalletsListRepository.userWalletsSync().size.toString(),
hasBackup = scanResponse.card.backupStatus?.isActive,
),
)
}
}
private fun setLoading(isLoading: Boolean) {
uiState.update { it.copy(isScanInProgress = isLoading) }
}
private fun handleScanError(error: TangemError) {
when (error) {
is TangemSdkError.NfcFeatureIsUnavailable -> handleNfcFeatureUnavailable()
is TangemSdkError -> Timber.e(error, "Scan error occurred")
else -> Timber.e(error, "Error happened")
}
}
private fun handleNfcFeatureUnavailable() {
uiMessageSender.send(
message = DialogMessage(
message = resourceReference(R.string.nfc_error_unavailable),
title = resourceReference(id = R.string.common_error),
),
)
}
}

View file

@ -0,0 +1,42 @@
package com.tangem.features.createwalletstart
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.components.SystemBarsIconsDisposable
import com.tangem.core.ui.res.ForceDarkTheme
import com.tangem.features.createwalletstart.ui.CreateWalletStartContent
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
internal class DefaultCreateWalletStartComponent @AssistedInject constructor(
@Assisted private val context: AppComponentContext,
@Assisted private val params: CreateWalletStartComponent.Params,
) : CreateWalletStartComponent, AppComponentContext by context {
private val model: CreateWalletStartModel = getOrCreateModel(params)
@Composable
override fun Content(modifier: Modifier) {
val state by model.uiState.collectAsStateWithLifecycle()
SystemBarsIconsDisposable(darkIcons = false)
ForceDarkTheme {
CreateWalletStartContent(
state = state,
modifier = modifier,
)
}
}
@AssistedFactory
interface Factory : CreateWalletStartComponent.Factory {
override fun create(
context: AppComponentContext,
params: CreateWalletStartComponent.Params,
): DefaultCreateWalletStartComponent
}
}

View file

@ -0,0 +1,33 @@
package com.tangem.features.createwalletstart.di
import com.tangem.core.decompose.model.Model
import com.tangem.features.createwalletstart.CreateWalletStartComponent
import com.tangem.features.createwalletstart.CreateWalletStartModel
import com.tangem.features.createwalletstart.DefaultCreateWalletStartComponent
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
@InstallIn(SingletonComponent::class)
internal object CreateWalletStartModule
@Module
@InstallIn(SingletonComponent::class)
internal interface CreateWalletStartModuleBinds {
@Binds
@Singleton
fun bindCreateWalletStartComponentFactory(
impl: DefaultCreateWalletStartComponent.Factory,
): CreateWalletStartComponent.Factory
@Binds
@IntoMap
@ClassKey(CreateWalletStartModel::class)
fun bindCreateWalletStartModel(model: CreateWalletStartModel): Model
}

View file

@ -0,0 +1,25 @@
package com.tangem.features.createwalletstart.entity
import com.tangem.core.ui.extensions.TextReference
import kotlinx.collections.immutable.ImmutableList
internal data class CreateWalletStartUM(
val title: TextReference,
val description: TextReference,
val featureItems: ImmutableList<FeatureItem>,
val imageResId: Int,
val isScanInProgress: Boolean,
val showScanSecondaryButton: Boolean,
val primaryButtonText: TextReference,
val onPrimaryButtonClick: () -> Unit,
val otherMethodDescription: TextReference,
val otherMethodTitle: TextReference,
val otherMethodClick: () -> Unit,
val onScanClick: () -> Unit,
val onBackClick: () -> Unit,
) {
data class FeatureItem(
val iconResId: Int,
val text: TextReference,
)
}

View file

@ -0,0 +1,486 @@
package com.tangem.features.createwalletstart.ui
import android.annotation.SuppressLint
import android.content.res.Configuration
import androidx.annotation.DrawableRes
import androidx.compose.foundation.*
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.material3.*
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.scale
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.PathEffect
import androidx.compose.ui.graphics.StrokeCap
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.layout.Layout
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
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.SecondaryButtonIconEnd
import com.tangem.core.ui.components.bottomFade
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.stringResourceSafe
import com.tangem.core.ui.res.TangemColorPalette
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.features.createwalletstart.entity.CreateWalletStartUM
import com.tangem.features.createwalletstart.impl.R
import kotlinx.collections.immutable.persistentListOf
import kotlin.math.max
@Suppress("LongMethod", "MagicNumber")
@OptIn(ExperimentalMaterial3Api::class, ExperimentalLayoutApi::class)
@Composable
internal fun CreateWalletStartContent(state: CreateWalletStartUM, modifier: Modifier = Modifier) {
Column(
modifier = modifier
.background(
brush = Brush.verticalGradient(
listOf(
TangemColorPalette.Dark6,
TangemColorPalette.Black,
),
),
)
.fillMaxSize()
.systemBarsPadding(),
horizontalAlignment = Alignment.CenterHorizontally,
) {
TopAppBar(
colors = TopAppBarDefaults.topAppBarColors(
containerColor = Color.Transparent,
),
navigationIcon = {
IconButton(onClick = state.onBackClick) {
Icon(
painter = painterResource(R.drawable.ic_back_24),
tint = TangemTheme.colors.icon.primary1,
contentDescription = null,
)
}
},
title = { },
)
Box(
modifier = Modifier
.weight(1f)
.bottomFade(height = 24.dp),
) {
AdaptiveScrollableContent(
topContent = {
Text(
modifier = Modifier
.fillMaxWidth()
.padding(
start = 32.dp,
top = 16.dp,
end = 32.dp,
),
text = state.title.resolveReference(),
style = TangemTheme.typography.h2,
color = TangemTheme.colors.text.primary1,
textAlign = TextAlign.Center,
)
Text(
modifier = Modifier
.fillMaxWidth()
.padding(
start = 32.dp,
top = 8.dp,
end = 32.dp,
),
text = state.description.resolveReference(),
style = TangemTheme.typography.body2,
color = TangemTheme.colors.text.primary1,
textAlign = TextAlign.Center,
)
FlowRow(
modifier = Modifier
.fillMaxWidth()
.padding(
start = 24.dp,
top = 16.dp,
end = 24.dp,
),
horizontalArrangement = Arrangement.Center,
verticalArrangement = Arrangement.spacedBy(8.dp),
) {
state.featureItems.forEach {
FeatureItem(
iconResId = it.iconResId,
text = it.text,
)
}
}
},
imageContent = {
Image(
modifier = Modifier
.fillMaxWidth()
.wrapContentHeight()
.padding(
vertical = 12.dp,
horizontal = 16.dp,
),
painter = painterResource(id = state.imageResId),
contentDescription = null,
contentScale = ContentScale.Fit,
)
},
bottomContent = {
if (state.showScanSecondaryButton) {
SecondaryButtonIconEnd(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp),
text = stringResourceSafe(R.string.welcome_unlock_card),
onClick = state.onScanClick,
showProgress = state.isScanInProgress,
iconResId = R.drawable.ic_tangem_24,
)
}
PrimaryButton(
modifier = Modifier
.fillMaxWidth()
.padding(
start = 16.dp,
top = 8.dp,
end = 16.dp,
),
text = state.primaryButtonText.resolveReference(),
onClick = state.onPrimaryButtonClick,
)
Row(
modifier = Modifier
.padding(
start = 16.dp,
top = 24.dp,
end = 16.dp,
),
horizontalArrangement = Arrangement.spacedBy(16.dp),
verticalAlignment = Alignment.CenterVertically,
) {
DashedGradientLine(
modifier = Modifier
.weight(1f)
.height(16.dp),
)
Text(
text = stringResourceSafe(R.string.welcome_create_wallet_other_method),
style = TangemTheme.typography.caption1,
color = TangemTheme.colors.text.secondary,
textAlign = TextAlign.Center,
)
DashedGradientLine(
modifier = Modifier
.weight(1f)
.height(16.dp)
.scale(scaleX = -1f, scaleY = 1f),
)
}
Text(
modifier = Modifier
.fillMaxWidth()
.padding(
start = 16.dp,
top = 16.dp,
end = 16.dp,
),
text = state.otherMethodDescription.resolveReference(),
style = TangemTheme.typography.subtitle2,
color = TangemTheme.colors.text.tertiary,
textAlign = TextAlign.Center,
)
Row(
modifier = Modifier
.wrapContentWidth()
.clickable { state.otherMethodClick() }
.padding(
horizontal = 16.dp,
vertical = 12.dp,
),
horizontalArrangement = Arrangement.Center,
) {
Text(
text = state.otherMethodTitle.resolveReference(),
style = TangemTheme.typography.subtitle1,
color = TangemTheme.colors.text.primary1,
textAlign = TextAlign.Center,
)
Icon(
painter = painterResource(id = R.drawable.ic_chevron_right_18x24),
tint = TangemTheme.colors.icon.primary1,
contentDescription = null,
)
}
},
minImageHeight = 160.dp,
)
}
if (!state.showScanSecondaryButton) {
FlowRow(
modifier = Modifier
.wrapContentWidth()
.padding(
start = 16.dp,
top = 24.dp,
end = 16.dp,
bottom = 8.dp,
),
horizontalArrangement = Arrangement.Center,
) {
Text(
text = stringResourceSafe(R.string.welcome_create_wallet_already_have),
style = TangemTheme.typography.caption1,
color = TangemTheme.colors.text.secondary,
)
Spacer(modifier = Modifier.size(4.dp))
Row(
modifier = Modifier
.clickable(
interactionSource = remember { MutableInteractionSource() },
indication = null,
) { state.onScanClick() },
verticalAlignment = Alignment.CenterVertically,
) {
Text(
text = stringResourceSafe(R.string.wallet_create_scan_title),
style = TangemTheme.typography.caption1,
color = TangemTheme.colors.text.primary1,
)
Spacer(modifier = Modifier.size(2.dp))
Icon(
modifier = Modifier.size(16.dp),
painter = painterResource(id = R.drawable.ic_tangem_24),
tint = TangemTheme.colors.icon.primary1,
contentDescription = null,
)
}
}
}
Spacer(modifier = Modifier.size(16.dp))
}
}
@SuppressLint("UnusedBoxWithConstraintsScope")
@Composable
private fun AdaptiveScrollableContent(
minImageHeight: Dp,
modifier: Modifier = Modifier,
topContent: @Composable () -> Unit,
imageContent: @Composable () -> Unit,
bottomContent: @Composable () -> Unit,
) {
BoxWithConstraints(
modifier = modifier.fillMaxSize(),
) {
val density = LocalDensity.current
val viewportHeight = maxHeight
val minImageHeightPx = with(density) { minImageHeight.roundToPx() }
val viewportHeightPx = with(density) { viewportHeight.roundToPx() }
Layout(
modifier = Modifier
.fillMaxWidth()
.verticalScroll(rememberScrollState()),
content = {
Column(
modifier = Modifier.fillMaxWidth(),
horizontalAlignment = Alignment.CenterHorizontally,
) {
topContent()
}
Box(
modifier = Modifier.fillMaxWidth(),
contentAlignment = Alignment.Center,
) {
imageContent()
}
Column(
modifier = Modifier.fillMaxWidth(),
horizontalAlignment = Alignment.CenterHorizontally,
) {
bottomContent()
}
},
) { measurables, constraints ->
val topPlaceable = measurables[0].measure(
constraints.copy(minHeight = 0, maxHeight = androidx.compose.ui.unit.Constraints.Infinity),
)
val bottomPlaceable = measurables[2].measure(
constraints.copy(minHeight = 0, maxHeight = androidx.compose.ui.unit.Constraints.Infinity),
)
val imageIntrinsicHeight = measurables[1].maxIntrinsicHeight(constraints.maxWidth)
val availableHeightForImage = max(0, viewportHeightPx - topPlaceable.height - bottomPlaceable.height)
val targetImageHeight = when {
imageIntrinsicHeight < minImageHeightPx -> minImageHeightPx
imageIntrinsicHeight > availableHeightForImage -> max(minImageHeightPx, availableHeightForImage)
else -> imageIntrinsicHeight
}
val imagePlaceable = measurables[1].measure(
constraints.copy(
minHeight = targetImageHeight,
maxHeight = targetImageHeight,
),
)
val totalContentHeight = topPlaceable.height + imagePlaceable.height + bottomPlaceable.height
layout(constraints.maxWidth, totalContentHeight) {
var yOffset = 0
topPlaceable.placeRelative(0, yOffset)
yOffset += topPlaceable.height
imagePlaceable.placeRelative(0, yOffset)
yOffset += imagePlaceable.height
bottomPlaceable.placeRelative(0, yOffset)
}
}
}
}
@Composable
private fun DashedGradientLine(modifier: Modifier = Modifier) {
val density = LocalDensity.current
val strokeColor = TangemTheme.colors.stroke.primary
Canvas(modifier = modifier) {
val strokePx = with(density) { 4.dp.toPx() }
val dashPx = with(density) { 4.dp.toPx() }
val gapPx = with(density) { 8.dp.toPx() }
val width = size.width
val centerY = size.height / 2
val brush = Brush.linearGradient(
colors = listOf(strokeColor.copy(alpha = 0f), strokeColor),
start = Offset(0f, 0f),
end = Offset(width, 0f),
)
val pathEffect = PathEffect.dashPathEffect(floatArrayOf(dashPx, gapPx), 0f)
drawLine(
brush = brush,
start = Offset(0f, centerY),
end = Offset(width, centerY),
strokeWidth = strokePx,
pathEffect = pathEffect,
cap = StrokeCap.Round,
)
}
}
@Composable
private fun FeatureItem(@DrawableRes iconResId: Int, text: TextReference) {
Row(
modifier = Modifier
.wrapContentWidth()
.padding(horizontal = 8.dp),
horizontalArrangement = Arrangement.spacedBy(6.dp),
) {
Icon(
modifier = Modifier.size(16.dp),
painter = painterResource(iconResId),
tint = TangemTheme.colors.icon.accent,
contentDescription = null,
)
Text(
text = text.resolveReference(),
style = TangemTheme.typography.caption1,
color = TangemTheme.colors.text.secondary,
)
}
}
private class CreateWalletStartStateProvider : CollectionPreviewParameterProvider<CreateWalletStartUM>(
collection = listOf(
CreateWalletStartUM(
title = resourceReference(R.string.common_tangem_wallet),
description = resourceReference(R.string.welcome_create_wallet_hardware_description),
featureItems = persistentListOf(
CreateWalletStartUM.FeatureItem(
iconResId = R.drawable.ic_shield_check_16,
text = resourceReference(R.string.welcome_create_wallet_feature_class),
),
CreateWalletStartUM.FeatureItem(
iconResId = R.drawable.ic_flash_16,
text = resourceReference(R.string.welcome_create_wallet_feature_delivery),
),
CreateWalletStartUM.FeatureItem(
iconResId = R.drawable.ic_sparkles_16,
text = resourceReference(R.string.welcome_create_wallet_feature_use),
),
),
imageResId = R.drawable.img_hardware_wallet,
showScanSecondaryButton = true,
onPrimaryButtonClick = { },
primaryButtonText = resourceReference(R.string.details_buy_wallet),
otherMethodTitle = resourceReference(R.string.welcome_create_wallet_mobile_title),
otherMethodDescription = resourceReference(
R.string.welcome_create_wallet_mobile_description,
),
otherMethodClick = { },
onBackClick = { },
onScanClick = { },
isScanInProgress = false,
),
CreateWalletStartUM(
title = resourceReference(R.string.hw_mobile_wallet),
description = resourceReference(R.string.welcome_create_wallet_mobile_description_full),
featureItems = persistentListOf(
CreateWalletStartUM.FeatureItem(
iconResId = R.drawable.ic_shield_check_16,
text = resourceReference(R.string.welcome_create_wallet_feature_seamless),
),
CreateWalletStartUM.FeatureItem(
iconResId = R.drawable.ic_flash_16,
text = resourceReference(R.string.welcome_create_wallet_feature_one_tap),
),
CreateWalletStartUM.FeatureItem(
iconResId = R.drawable.ic_stack_fill_new_16,
text = resourceReference(R.string.welcome_create_wallet_feature_assets),
),
),
imageResId = R.drawable.img_mobile_wallet,
showScanSecondaryButton = false,
onPrimaryButtonClick = { },
primaryButtonText = resourceReference(R.string.welcome_create_wallet_mobile_title),
otherMethodTitle = resourceReference(R.string.welcome_create_wallet_use_hardware_title),
otherMethodDescription = resourceReference(
R.string.welcome_create_wallet_use_hardware_description,
),
otherMethodClick = { },
onBackClick = { },
onScanClick = { },
isScanInProgress = false,
),
),
)
@Preview(showBackground = true, widthDp = 360, heightDp = 480, uiMode = Configuration.UI_MODE_NIGHT_YES)
@Preview(showBackground = true, widthDp = 360, heightDp = 560, uiMode = Configuration.UI_MODE_NIGHT_YES)
@Preview(showBackground = true, widthDp = 360, heightDp = 720, uiMode = Configuration.UI_MODE_NIGHT_YES)
@Preview(showBackground = true, widthDp = 360, heightDp = 840, uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun PreviewCreateWalletStartContent(
@PreviewParameter(CreateWalletStartStateProvider::class) param: CreateWalletStartUM,
) {
TangemThemePreview {
CreateWalletStartContent(
state = param,
)
}
}