diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml
index e644cefa16..c0d7a0dfb5 100644
--- a/core/res/src/main/res/values/strings.xml
+++ b/core/res/src/main/res/values/strings.xml
@@ -115,10 +115,17 @@
Enter address
Invalid address
Keep editing
+ You can not create more than 20 addresses. Delete one to add new.
+ Can\'t add new address
+ Contact name is required
+ Contact name contains invalid characters
+ Contact name must not exceed 50 characters
+ That name is already taken on this wallet
New contact
No contacts yet
Contacts added will appear here
Remove address
+ Save to Wallet
This contact will be linked to this wallet’s address book.
Select network
Address book
@@ -352,6 +359,7 @@
Get token
Go to provider
Go to token
+ Go to verification
Got it
Hide
Hold to %s
@@ -732,6 +740,8 @@
Key Generation
All cryptographic operations happen inside the secure chip, certified against cloning and physical tampering.
Hardware-Level Security
+ Network activity is high. You can continue now or try again later when fees may be lower.
+ Network fee is higher than usual
Add Existing Wallet
Create New Wallet
Order Tangem
@@ -1402,6 +1412,7 @@
Memo
Check your network connection
Network fee info unreachable
+ from %1$s in %2$s
You send
From %s
Gas limit
diff --git a/features/details/impl/src/test/kotlin/com/tangem/features/details/model/DetailsModelTestBase.kt b/features/details/impl/src/test/kotlin/com/tangem/features/details/model/DetailsModelTestBase.kt
index d40b5d6c0a..072a256769 100644
--- a/features/details/impl/src/test/kotlin/com/tangem/features/details/model/DetailsModelTestBase.kt
+++ b/features/details/impl/src/test/kotlin/com/tangem/features/details/model/DetailsModelTestBase.kt
@@ -16,6 +16,8 @@ import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.pay.TangemPayEligibilityManager
import com.tangem.domain.tangempay.GetTangemPayCustomerIdUseCase
+import com.tangem.domain.virtualaccount.model.VirtualAccountEligibility
+import com.tangem.domain.virtualaccount.usecase.GetVirtualAccountEligibilityUseCase
import com.tangem.domain.walletconnect.CheckIsWalletConnectAvailableUseCase
import com.tangem.domain.wallets.usecase.GenerateBuyTangemCardLinkUseCase
import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase
@@ -27,12 +29,7 @@ import com.tangem.features.details.utils.ItemsBuilder
import com.tangem.features.details.utils.SocialsBuilder
import com.tangem.utils.coroutines.TestingCoroutineDispatcherProvider
import com.tangem.utils.info.AppInfoProvider
-import io.mockk.coEvery
-import io.mockk.every
-import io.mockk.mockk
-import io.mockk.mockkObject
-import io.mockk.slot
-import io.mockk.unmockkObject
+import io.mockk.*
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -66,6 +63,7 @@ internal abstract class DetailsModelTestBase {
protected val generateBuyTangemCardLinkUseCase: GenerateBuyTangemCardLinkUseCase = mockk()
protected val analyticsEventHandler: AnalyticsEventHandler = mockk(relaxUnitFun = true)
protected val tangemPayEligibilityManager: TangemPayEligibilityManager = mockk()
+ protected val getVirtualAccountEligibilityUseCase: GetVirtualAccountEligibilityUseCase = mockk()
// Captured from itemsBuilder.buildAll(...) so the feature buttons can be driven.
protected val wcSlot = slot()
@@ -89,6 +87,7 @@ internal abstract class DetailsModelTestBase {
every { appInfoProvider.appVersion } returns "1.2.3"
every { appInfoProvider.appVersionCode } returns 456
coEvery { tangemPayEligibilityManager.getEligibleWallets(any(), any()) } returns emptyList()
+ coEvery { getVirtualAccountEligibilityUseCase(any()) } returns VirtualAccountEligibility.NotAvailable
every {
itemsBuilder.buildAll(
@@ -128,6 +127,7 @@ internal abstract class DetailsModelTestBase {
generateBuyTangemCardLinkUseCase = generateBuyTangemCardLinkUseCase,
analyticsEventHandler = analyticsEventHandler,
tangemPayEligibilityManager = tangemPayEligibilityManager,
+ getVirtualAccountEligibilityUseCase = getVirtualAccountEligibilityUseCase,
)
protected fun stubBuildAllReturns(list: ImmutableList) {
diff --git a/features/virtual-accounts/details/impl/build.gradle.kts b/features/virtual-accounts/details/impl/build.gradle.kts
index 1d9448064a..0993a9f074 100644
--- a/features/virtual-accounts/details/impl/build.gradle.kts
+++ b/features/virtual-accounts/details/impl/build.gradle.kts
@@ -1,6 +1,7 @@
plugins {
alias(deps.plugins.android.library)
alias(deps.plugins.kotlin.android)
+ alias(deps.plugins.kotlin.serialization)
alias(deps.plugins.kotlin.kapt)
alias(deps.plugins.hilt.android)
id("configuration")
@@ -14,12 +15,15 @@ dependencies {
/** Core */
implementation(projects.core.configToggles)
implementation(projects.core.decompose)
+ implementation(projects.core.navigation)
implementation(projects.core.res)
implementation(projects.core.ui)
implementation(projects.core.utils)
/** Domain */
implementation(projects.domain.models)
+ implementation(projects.domain.feedback)
+ implementation(projects.domain.feedback.models)
/** Features */
implementation(projects.features.virtualAccounts.details.api)
@@ -31,6 +35,10 @@ dependencies {
implementation(deps.compose.ui.tooling)
implementation(deps.decompose.ext.compose)
+ /** Other */
+ implementation(deps.arrow.core)
+ implementation(deps.kotlin.immutable.collections)
+
/** DI */
implementation(deps.hilt.android)
kapt(deps.hilt.kapt)
diff --git a/features/virtual-accounts/details/impl/src/main/kotlin/com/tangem/features/virtualaccount/main/DefaultVirtualAccountMainComponent.kt b/features/virtual-accounts/details/impl/src/main/kotlin/com/tangem/features/virtualaccount/main/DefaultVirtualAccountMainComponent.kt
index 7ad7ef7c21..50ce82e412 100644
--- a/features/virtual-accounts/details/impl/src/main/kotlin/com/tangem/features/virtualaccount/main/DefaultVirtualAccountMainComponent.kt
+++ b/features/virtual-accounts/details/impl/src/main/kotlin/com/tangem/features/virtualaccount/main/DefaultVirtualAccountMainComponent.kt
@@ -4,24 +4,56 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.lifecycle.compose.collectAsStateWithLifecycle
+import com.arkivanov.decompose.ComponentContext
+import com.arkivanov.decompose.extensions.compose.subscribeAsState
+import com.arkivanov.decompose.router.slot.childSlot
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.ui.decompose.ComposableBottomSheetComponent
import com.tangem.features.virtualaccount.details.component.VirtualAccountMainComponent
+import com.tangem.features.virtualaccount.main.addfunds.VirtualAccountAddFundsBottomSheetComponent
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
internal class DefaultVirtualAccountMainComponent @AssistedInject constructor(
@Assisted appComponentContext: AppComponentContext,
- @Assisted params: VirtualAccountMainComponent.Params,
+ @Assisted private val params: VirtualAccountMainComponent.Params,
) : VirtualAccountMainComponent, AppComponentContext by appComponentContext {
private val model: VirtualAccountMainModel = getOrCreateModel(params = params)
+ private val bottomSheetSlot = childSlot(
+ source = model.bottomSheetNavigation,
+ serializer = VirtualAccountMainNavigationBottomSheetConfig.serializer(),
+ handleBackButton = false,
+ childFactory = ::bottomSheetChild,
+ )
+
@Composable
override fun Content(modifier: Modifier) {
val state by model.uiState.collectAsStateWithLifecycle()
+ val bottomSheet by bottomSheetSlot.subscribeAsState()
VirtualAccountMainScreen(state = state, modifier = modifier)
+ bottomSheet.child?.instance?.BottomSheet()
+ }
+
+ private fun bottomSheetChild(
+ config: VirtualAccountMainNavigationBottomSheetConfig,
+ componentContext: ComponentContext,
+ ): ComposableBottomSheetComponent {
+ return when (config) {
+ is VirtualAccountMainNavigationBottomSheetConfig.AddFunds -> VirtualAccountAddFundsBottomSheetComponent(
+ appComponentContext = childByContext(componentContext),
+ params = VirtualAccountAddFundsBottomSheetComponent.Params(
+ userWalletId = params.userWalletId,
+ listener = model,
+ requisites = config.requisites,
+ dailyDepositLimit = config.dailyDepositLimit,
+ ),
+ )
+ }
}
@AssistedFactory
diff --git a/features/virtual-accounts/details/impl/src/main/kotlin/com/tangem/features/virtualaccount/main/VirtualAccountMainModel.kt b/features/virtual-accounts/details/impl/src/main/kotlin/com/tangem/features/virtualaccount/main/VirtualAccountMainModel.kt
index 65a9fd1e60..a05c327e16 100644
--- a/features/virtual-accounts/details/impl/src/main/kotlin/com/tangem/features/virtualaccount/main/VirtualAccountMainModel.kt
+++ b/features/virtual-accounts/details/impl/src/main/kotlin/com/tangem/features/virtualaccount/main/VirtualAccountMainModel.kt
@@ -1,6 +1,9 @@
package com.tangem.features.virtualaccount.main
import androidx.compose.runtime.Stable
+import com.arkivanov.decompose.router.slot.SlotNavigation
+import com.arkivanov.decompose.router.slot.activate
+import com.arkivanov.decompose.router.slot.dismiss
import com.tangem.core.decompose.di.ModelScoped
import com.tangem.core.decompose.model.Model
import com.tangem.core.decompose.model.ParamsContainer
@@ -9,6 +12,8 @@ import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.stringReference
import com.tangem.features.virtualaccount.details.component.VirtualAccountMainComponent
import com.tangem.features.virtualaccount.details.impl.R
+import com.tangem.features.virtualaccount.main.addfunds.VirtualAccountAddFundsBottomSheetComponent
+import com.tangem.features.virtualaccount.main.addfunds.VirtualAccountAddFundsListener
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
@@ -20,16 +25,22 @@ internal class VirtualAccountMainModel @Inject constructor(
paramsContainer: ParamsContainer,
override val dispatchers: CoroutineDispatcherProvider,
private val router: Router,
-) : Model() {
+) : Model(), VirtualAccountAddFundsListener {
@Suppress("UnusedPrivateProperty")
private val params = paramsContainer.require()
+ val bottomSheetNavigation: SlotNavigation = SlotNavigation()
+
val uiState: StateFlow
field = MutableStateFlow(
createInitialState(),
)
+ override fun onAddFundsDismiss() {
+ bottomSheetNavigation.dismiss()
+ }
+
private fun createInitialState(): VirtualAccountMainUM = VirtualAccountMainUM(
title = resourceReference(R.string.virtual_account_title),
subtitle = resourceReference(R.string.tangempay_usdc_on_polygon_network),
@@ -40,7 +51,63 @@ internal class VirtualAccountMainModel @Inject constructor(
isBalanceHidden = false,
onBackClick = { router.pop() },
onMenuClick = {},
- onAddFundsClick = {},
+ onAddFundsClick = ::onAddFundsClick,
onSendClick = {},
)
+
+ private fun buildRequisites(details: VirtualAccountDepositDetails) = listOf(
+ VirtualAccountAddFundsBottomSheetComponent.RequisitesRow(
+ title = stringReference("Beneficiary name and address"),
+ titleForShare = "Beneficiary name and address",
+ value = "${details.beneficiaryName}\n${details.beneficiaryAddress}",
+ ),
+ VirtualAccountAddFundsBottomSheetComponent.RequisitesRow(
+ title = stringReference("Bank name and address"),
+ titleForShare = "Bank name and address",
+ value = "${details.bankName}\n${details.bankAddress}",
+ ),
+ VirtualAccountAddFundsBottomSheetComponent.RequisitesRow(
+ title = stringReference("Account number"),
+ titleForShare = "Account number",
+ value = details.accountNumber,
+ ),
+ VirtualAccountAddFundsBottomSheetComponent.RequisitesRow(
+ title = stringReference("Routing number"),
+ titleForShare = "Routing number",
+ value = details.routingNumber,
+ ),
+ )
+
+ private fun onAddFundsClick() {
+ val details = getDepositDetails()
+ bottomSheetNavigation.activate(
+ VirtualAccountMainNavigationBottomSheetConfig.AddFunds(
+ requisites = buildRequisites(details),
+ dailyDepositLimit = details.dailyDepositLimit,
+ ),
+ )
+ }
+
+ // TODO v_rodionov: HARDCODE - get this data from backend
+ private fun getDepositDetails(): VirtualAccountDepositDetails {
+ return VirtualAccountDepositDetails(
+ beneficiaryName = "Ivan Ivanov",
+ beneficiaryAddress = "18, Rue Rubens 20, Paris, Ile-de-France 75013, US",
+ bankName = "SSB Bank",
+ bankAddress = "8700 Perry Highway, Pittsburgh, PA 15237, US",
+ accountNumber = "707613210122",
+ routingNumber = "043087080",
+ dailyDepositLimit = "$10,000",
+ )
+ }
+
+ private data class VirtualAccountDepositDetails(
+ val beneficiaryName: String,
+ val beneficiaryAddress: String,
+ val bankName: String,
+ val bankAddress: String,
+ val accountNumber: String,
+ val routingNumber: String,
+ val dailyDepositLimit: String,
+ )
}
\ No newline at end of file
diff --git a/features/virtual-accounts/details/impl/src/main/kotlin/com/tangem/features/virtualaccount/main/VirtualAccountMainNavigationBottomSheetConfig.kt b/features/virtual-accounts/details/impl/src/main/kotlin/com/tangem/features/virtualaccount/main/VirtualAccountMainNavigationBottomSheetConfig.kt
new file mode 100644
index 0000000000..f0de59ec19
--- /dev/null
+++ b/features/virtual-accounts/details/impl/src/main/kotlin/com/tangem/features/virtualaccount/main/VirtualAccountMainNavigationBottomSheetConfig.kt
@@ -0,0 +1,12 @@
+package com.tangem.features.virtualaccount.main
+
+import com.tangem.features.virtualaccount.main.addfunds.VirtualAccountAddFundsBottomSheetComponent.RequisitesRow
+import kotlinx.serialization.Serializable
+
+@Serializable
+internal sealed interface VirtualAccountMainNavigationBottomSheetConfig {
+ data class AddFunds(
+ val requisites: List,
+ val dailyDepositLimit: String,
+ ) : VirtualAccountMainNavigationBottomSheetConfig
+}
\ No newline at end of file
diff --git a/features/virtual-accounts/details/impl/src/main/kotlin/com/tangem/features/virtualaccount/main/addfunds/VirtualAccountAddFundsBottomSheet.kt b/features/virtual-accounts/details/impl/src/main/kotlin/com/tangem/features/virtualaccount/main/addfunds/VirtualAccountAddFundsBottomSheet.kt
new file mode 100644
index 0000000000..4537376d28
--- /dev/null
+++ b/features/virtual-accounts/details/impl/src/main/kotlin/com/tangem/features/virtualaccount/main/addfunds/VirtualAccountAddFundsBottomSheet.kt
@@ -0,0 +1,328 @@
+package com.tangem.features.virtualaccount.main.addfunds
+
+import android.content.res.Configuration
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.background
+import androidx.compose.foundation.border
+import androidx.compose.foundation.layout.*
+import androidx.compose.foundation.shape.CircleShape
+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.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import com.tangem.core.ui.components.bottomsheets.TangemBottomSheet
+import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
+import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent
+import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetType
+import com.tangem.core.ui.ds.image.TangemIconUM
+import com.tangem.core.ui.ds.topbar.TangemTopBar
+import com.tangem.core.ui.ds.topbar.TangemTopBarType
+import com.tangem.core.ui.ds2.button.Close
+import com.tangem.core.ui.ds2.button.TangemButton
+import com.tangem.core.ui.ds2.row.*
+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.stringReference
+import com.tangem.core.ui.res.TangemTheme
+import com.tangem.core.ui.res.TangemThemePreviewRedesign
+import com.tangem.core.ui.res.generated.icons.Icons
+import com.tangem.core.ui.res.generated.icons.ic_copy_24
+import com.tangem.core.ui.res.generated.icons.ic_info_24
+import com.tangem.core.ui.res.generated.icons.ic_sign_usd_32
+import com.tangem.features.virtualaccount.details.impl.R
+import kotlinx.collections.immutable.persistentListOf
+import com.tangem.core.ui.R as CoreUiR
+
+@Composable
+internal fun VirtualAccountAddFundsBottomSheet(state: VirtualAccountAddFundsUM) {
+ val title = stringReference("Account details")
+ .takeIf { state.content is VirtualAccountAddFundsUM.Content.Details }
+
+ TangemBottomSheet(
+ config = TangemBottomSheetConfig(
+ isShown = true,
+ onDismissRequest = state.onDismiss,
+ content = TangemBottomSheetConfigContent.Empty,
+ ),
+ type = TangemBottomSheetType.Modal,
+ containerColor = TangemTheme.colors3.bg.secondary,
+ title = {
+ TangemTopBar(
+ type = TangemTopBarType.BottomSheet,
+ title = title,
+ endContent = { TangemButton.Close(onClick = state.onDismiss) },
+ )
+ },
+ content = { _ ->
+ when (val content = state.content) {
+ is VirtualAccountAddFundsUM.Content.Intro -> IntroContent(content)
+ is VirtualAccountAddFundsUM.Content.Details -> DetailsContent(content)
+ }
+ },
+ )
+}
+
+@Composable
+private fun IntroContent(content: VirtualAccountAddFundsUM.Content.Intro, modifier: Modifier = Modifier) {
+ Column(
+ modifier = modifier
+ .fillMaxWidth()
+ .padding(horizontal = TangemTheme.dimens2.x4)
+ .padding(bottom = TangemTheme.dimens2.x4),
+ horizontalAlignment = Alignment.CenterHorizontally,
+ ) {
+ IntroIcons(modifier = Modifier.padding(top = TangemTheme.dimens2.x4))
+ TitleText(
+ text = stringReference("Received USD will be converted to USDC by 1:1 rate"),
+ modifier = Modifier.padding(top = TangemTheme.dimens2.x8),
+ )
+ SubtitleText(
+ text = stringReference("It might take 1-3 days to receive the money"),
+ modifier = Modifier.padding(top = TangemTheme.dimens2.x2),
+ )
+ InfoNotification(
+ title = stringReference("Only ACH and domestic wire transfers are available"),
+ subtitle = stringReference("SWIFT won't pass"),
+ modifier = Modifier.padding(top = TangemTheme.dimens2.x6),
+ )
+ TangemButton(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(top = TangemTheme.dimens2.x4),
+ text = stringReference("Show details"),
+ variant = TangemButton.Variant.Primary,
+ size = TangemButton.Size.X12,
+ onClick = content.onShowDetailsClick,
+ )
+ }
+}
+
+@Composable
+private fun DetailsContent(content: VirtualAccountAddFundsUM.Content.Details, modifier: Modifier = Modifier) {
+ Column(
+ modifier = modifier
+ .fillMaxWidth()
+ .padding(bottom = TangemTheme.dimens2.x4),
+ ) {
+ content.items.forEachIndexed { index, item ->
+ CopyableRow(
+ item = item,
+ divider = index != content.items.lastIndex,
+ )
+ }
+ InfoNotification(
+ title = stringReference("Available to deposit per day: ${content.dailyLimit}"),
+ subtitle = stringReference("Limit is resetting every day"),
+ modifier = Modifier
+ .padding(horizontal = TangemTheme.dimens2.x4)
+ .padding(top = TangemTheme.dimens2.x3),
+ )
+ TangemButton(
+ text = resourceReference(R.string.common_share),
+ variant = TangemButton.Variant.Primary,
+ size = TangemButton.Size.X12,
+ onClick = content.onShareClick,
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(horizontal = TangemTheme.dimens2.x4)
+ .padding(top = TangemTheme.dimens2.x4),
+ )
+ }
+}
+
+@Composable
+private fun CopyableRow(item: VirtualAccountAddFundsUM.DetailItem, divider: Boolean, modifier: Modifier = Modifier) {
+ TangemRow(
+ modifier = modifier,
+ divider = divider,
+ contentLead = TangemRowContentLead.Start,
+ verticalAlignment = TangemRowVerticalAlignment.Center,
+ titleSlot = {
+ TangemRowText(
+ text = item.label,
+ role = TangemRowTextRole.Subtitle,
+ )
+ },
+ subtitleSlot = {
+ TangemRowText(
+ text = item.value,
+ role = TangemRowTextRole.Title,
+ maxLines = Int.MAX_VALUE,
+ )
+ },
+ endSlot = {
+ TangemButton(
+ iconStart = TangemIconUM.Icon(imageVector = Icons.ic_copy_24),
+ onClick = item.onCopyClick,
+ size = TangemButton.Size.X9,
+ variant = TangemButton.Variant.Ghost,
+ contentDescription = item.label.resolveReference(),
+ )
+ },
+ )
+}
+
+@Composable
+private fun InfoNotification(title: TextReference, subtitle: TextReference, modifier: Modifier = Modifier) {
+ Row(
+ modifier = modifier
+ .fillMaxWidth()
+ .clip(RoundedCornerShape(TangemTheme.dimens2.x4))
+ .background(TangemTheme.colors3.bg.status.infoSubtle)
+ .padding(TangemTheme.dimens2.x4),
+ horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens2.x2),
+ ) {
+ Icon(
+ modifier = Modifier.size(TangemTheme.dimens2.x5),
+ imageVector = Icons.ic_info_24,
+ contentDescription = null,
+ tint = TangemTheme.colors3.icon.status.info,
+ )
+ Column(verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens2.x0_5)) {
+ Text(
+ text = title.resolveReference(),
+ style = TangemTheme.typography3.subheading.medium,
+ color = TangemTheme.colors3.text.primary,
+ )
+ Text(
+ text = subtitle.resolveReference(),
+ style = TangemTheme.typography3.caption.medium,
+ color = TangemTheme.colors3.text.secondary,
+ )
+ }
+ }
+}
+
+@Composable
+private fun IntroIcons(modifier: Modifier = Modifier) {
+ Row(
+ modifier = modifier,
+ horizontalArrangement = Arrangement.spacedBy(-TangemTheme.dimens2.x4),
+ ) {
+ UsdIcon()
+ UsdcIcon()
+ }
+}
+
+@Composable
+private fun UsdIcon(modifier: Modifier = Modifier) {
+ Box(
+ modifier = modifier
+ .size(TangemTheme.dimens2.x20)
+ .clip(CircleShape)
+ .background(TangemTheme.colors3.bg.opaque.primary)
+ .border(width = 1.dp, color = TangemTheme.colors3.border.secondary, shape = CircleShape),
+ contentAlignment = Alignment.Center,
+ ) {
+ Icon(
+ modifier = Modifier.size(TangemTheme.dimens2.x8),
+ imageVector = Icons.ic_sign_usd_32,
+ contentDescription = null,
+ tint = TangemTheme.colors3.icon.primary,
+ )
+ }
+}
+
+@Composable
+private fun UsdcIcon(modifier: Modifier = Modifier) {
+ Box(modifier = modifier.size(TangemTheme.dimens2.x20)) {
+ Image(
+ modifier = Modifier
+ .fillMaxSize()
+ .clip(CircleShape)
+ .border(width = 1.dp, color = TangemTheme.colors3.border.secondary, shape = CircleShape),
+ painter = painterResource(CoreUiR.drawable.img_usdc_16),
+ contentDescription = null,
+ )
+ Box(
+ modifier = Modifier
+ .align(Alignment.TopEnd)
+ .size(TangemTheme.dimens2.x6)
+ .background(color = TangemTheme.colors3.bg.accent.violet, shape = CircleShape)
+ .border(
+ width = TangemTheme.dimens2.x0_5,
+ color = TangemTheme.colors3.bg.secondary,
+ shape = CircleShape,
+ ),
+ contentAlignment = Alignment.Center,
+ ) {
+ Icon(
+ modifier = Modifier.size(TangemTheme.dimens2.x4),
+ painter = painterResource(CoreUiR.drawable.ic_polygon_22),
+ contentDescription = null,
+ tint = TangemTheme.colors3.icon.inverse,
+ )
+ }
+ }
+}
+
+@Composable
+private fun TitleText(text: TextReference, modifier: Modifier = Modifier) {
+ Text(
+ modifier = modifier.fillMaxWidth(),
+ text = text.resolveReference(),
+ style = TangemTheme.typography3.heading.small,
+ color = TangemTheme.colors3.text.primary,
+ textAlign = TextAlign.Center,
+ )
+}
+
+@Composable
+private fun SubtitleText(text: TextReference, modifier: Modifier = Modifier) {
+ Text(
+ modifier = modifier.fillMaxWidth(),
+ text = text.resolveReference(),
+ style = TangemTheme.typography3.subheading.medium,
+ color = TangemTheme.colors3.text.secondary,
+ textAlign = TextAlign.Center,
+ )
+}
+
+@Preview(showBackground = true)
+@Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES)
+@Composable
+private fun VirtualAccountAddFundsIntroPreview() {
+ TangemThemePreviewRedesign {
+ IntroContent(
+ content = VirtualAccountAddFundsUM.Content.Intro(
+ onShowDetailsClick = {},
+ ),
+ modifier = Modifier.background(TangemTheme.colors3.bg.secondary),
+ )
+ }
+}
+
+@Preview(showBackground = true)
+@Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES)
+@Composable
+private fun VirtualAccountAddFundsDetailsPreview() {
+ TangemThemePreviewRedesign {
+ DetailsContent(
+ content = VirtualAccountAddFundsUM.Content.Details(
+ items = persistentListOf(
+ VirtualAccountAddFundsUM.DetailItem(
+ label = stringReference("Beneficiary name and address"),
+ value = "Ivan Ivanov\n18, Rue Rubens 20, Paris, Ile-de-France 75013, US",
+ onCopyClick = {},
+ ),
+ VirtualAccountAddFundsUM.DetailItem(
+ label = stringReference("Account number"),
+ value = "707613210122",
+ onCopyClick = {},
+ ),
+ ),
+ dailyLimit = "$10,000",
+ onShareClick = {},
+ ),
+ modifier = Modifier.background(TangemTheme.colors3.bg.secondary),
+ )
+ }
+}
\ No newline at end of file
diff --git a/features/virtual-accounts/details/impl/src/main/kotlin/com/tangem/features/virtualaccount/main/addfunds/VirtualAccountAddFundsBottomSheetComponent.kt b/features/virtual-accounts/details/impl/src/main/kotlin/com/tangem/features/virtualaccount/main/addfunds/VirtualAccountAddFundsBottomSheetComponent.kt
new file mode 100644
index 0000000000..4faaffcba5
--- /dev/null
+++ b/features/virtual-accounts/details/impl/src/main/kotlin/com/tangem/features/virtualaccount/main/addfunds/VirtualAccountAddFundsBottomSheetComponent.kt
@@ -0,0 +1,45 @@
+package com.tangem.features.virtualaccount.main.addfunds
+
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+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.ComposableBottomSheetComponent
+import com.tangem.core.ui.extensions.TextReference
+import com.tangem.domain.models.wallet.UserWalletId
+
+internal class VirtualAccountAddFundsBottomSheetComponent(
+ appComponentContext: AppComponentContext,
+ params: Params,
+) : ComposableBottomSheetComponent, AppComponentContext by appComponentContext {
+
+ private val model: VirtualAccountAddFundsModel = getOrCreateModel(params = params)
+
+ override fun dismiss() {
+ model.onDismiss()
+ }
+
+ @Composable
+ override fun BottomSheet() {
+ val state by model.uiState.collectAsStateWithLifecycle()
+ VirtualAccountAddFundsBottomSheet(state = state)
+ }
+
+ data class Params(
+ val userWalletId: UserWalletId,
+ val requisites: List,
+ val dailyDepositLimit: String,
+ val listener: VirtualAccountAddFundsListener,
+ )
+
+ data class RequisitesRow(
+ val title: TextReference,
+ val titleForShare: String,
+ val value: String,
+ )
+}
+
+internal interface VirtualAccountAddFundsListener {
+ fun onAddFundsDismiss()
+}
\ No newline at end of file
diff --git a/features/virtual-accounts/details/impl/src/main/kotlin/com/tangem/features/virtualaccount/main/addfunds/VirtualAccountAddFundsModel.kt b/features/virtual-accounts/details/impl/src/main/kotlin/com/tangem/features/virtualaccount/main/addfunds/VirtualAccountAddFundsModel.kt
new file mode 100644
index 0000000000..d78ef65a65
--- /dev/null
+++ b/features/virtual-accounts/details/impl/src/main/kotlin/com/tangem/features/virtualaccount/main/addfunds/VirtualAccountAddFundsModel.kt
@@ -0,0 +1,70 @@
+package com.tangem.features.virtualaccount.main.addfunds
+
+import androidx.compose.runtime.Stable
+import androidx.compose.ui.util.fastForEach
+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.navigation.share.ShareManager
+import com.tangem.core.ui.clipboard.ClipboardManager
+import com.tangem.core.ui.extensions.TextReference
+import com.tangem.utils.coroutines.CoroutineDispatcherProvider
+import kotlinx.collections.immutable.toImmutableList
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.update
+import javax.inject.Inject
+
+@Stable
+@ModelScoped
+internal class VirtualAccountAddFundsModel @Inject constructor(
+ paramsContainer: ParamsContainer,
+ override val dispatchers: CoroutineDispatcherProvider,
+ private val clipboardManager: ClipboardManager,
+ private val shareManager: ShareManager,
+) : Model() {
+
+ private val params = paramsContainer.require()
+
+ val uiState: StateFlow
+ field = MutableStateFlow(
+ VirtualAccountAddFundsUM(
+ onDismiss = ::onDismiss,
+ content = VirtualAccountAddFundsUM.Content.Intro(
+ onShowDetailsClick = { showDetailsContent() },
+ ),
+ ),
+ )
+
+ fun onDismiss() {
+ params.listener.onAddFundsDismiss()
+ }
+
+ private fun showDetailsContent() {
+ uiState.update { state ->
+ state.copy(
+ content = VirtualAccountAddFundsUM.Content.Details(
+ items = params.requisites
+ .map { detailItem(label = it.title, value = it.value) }
+ .toImmutableList(),
+ dailyLimit = params.dailyDepositLimit,
+ onShareClick = { shareManager.shareText(buildShareText()) },
+ ),
+ )
+ }
+ }
+
+ private fun detailItem(label: TextReference, value: String) = VirtualAccountAddFundsUM.DetailItem(
+ label = label,
+ value = value,
+ onCopyClick = { clipboardManager.setText(text = value, isSensitive = true) },
+ )
+
+ private fun buildShareText(): String {
+ return buildString {
+ params.requisites.fastForEach { item ->
+ appendLine("${item.titleForShare}: ${item.value}")
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/features/virtual-accounts/details/impl/src/main/kotlin/com/tangem/features/virtualaccount/main/addfunds/VirtualAccountAddFundsUM.kt b/features/virtual-accounts/details/impl/src/main/kotlin/com/tangem/features/virtualaccount/main/addfunds/VirtualAccountAddFundsUM.kt
new file mode 100644
index 0000000000..4665eddc8f
--- /dev/null
+++ b/features/virtual-accounts/details/impl/src/main/kotlin/com/tangem/features/virtualaccount/main/addfunds/VirtualAccountAddFundsUM.kt
@@ -0,0 +1,33 @@
+package com.tangem.features.virtualaccount.main.addfunds
+
+import androidx.compose.runtime.Immutable
+import com.tangem.core.ui.extensions.TextReference
+import kotlinx.collections.immutable.ImmutableList
+
+@Immutable
+internal data class VirtualAccountAddFundsUM(
+ val onDismiss: () -> Unit,
+ val content: Content,
+) {
+
+ @Immutable
+ sealed interface Content {
+
+ data class Intro(
+ val onShowDetailsClick: () -> Unit,
+ ) : Content
+
+ data class Details(
+ val items: ImmutableList,
+ val dailyLimit: String,
+ val onShareClick: () -> Unit,
+ ) : Content
+ }
+
+ @Immutable
+ data class DetailItem(
+ val label: TextReference,
+ val value: String,
+ val onCopyClick: () -> Unit,
+ )
+}
\ No newline at end of file
diff --git a/features/virtual-accounts/details/impl/src/main/kotlin/com/tangem/features/virtualaccount/main/di/VirtualAccountMainModelModule.kt b/features/virtual-accounts/details/impl/src/main/kotlin/com/tangem/features/virtualaccount/main/di/VirtualAccountMainModelModule.kt
index 9c621bc6fc..4b85e1f7d4 100644
--- a/features/virtual-accounts/details/impl/src/main/kotlin/com/tangem/features/virtualaccount/main/di/VirtualAccountMainModelModule.kt
+++ b/features/virtual-accounts/details/impl/src/main/kotlin/com/tangem/features/virtualaccount/main/di/VirtualAccountMainModelModule.kt
@@ -3,6 +3,7 @@ package com.tangem.features.virtualaccount.main.di
import com.tangem.core.decompose.di.ModelComponent
import com.tangem.core.decompose.model.Model
import com.tangem.features.virtualaccount.main.VirtualAccountMainModel
+import com.tangem.features.virtualaccount.main.addfunds.VirtualAccountAddFundsModel
import dagger.Binds
import dagger.Module
import dagger.hilt.InstallIn
@@ -17,4 +18,9 @@ internal interface VirtualAccountMainModelModule {
@IntoMap
@ClassKey(VirtualAccountMainModel::class)
fun bindVirtualAccountMainModel(model: VirtualAccountMainModel): Model
+
+ @Binds
+ @IntoMap
+ @ClassKey(VirtualAccountAddFundsModel::class)
+ fun bindVirtualAccountAddFundsModel(model: VirtualAccountAddFundsModel): Model
}
\ No newline at end of file